What is best practice for move semantics with virtual functions?
Asked Answered
H

0

6

So let's say i have interface:

class MyInterface
{
public:
    virtual ~MyInterface() = default;
    virtual void DoSomething(const MyType& a, const MyTypeB& b);
};

And the thing i want is to allow usage of move semantics if any of function arguments is rvalue or lvalue reference.

What i don't want is to define interface like this:

class MyInterface
{
public:
    virtual ~MyInterface() = default;
    virtual void DoSomething(const MyType& a, const MyTypeB& b);
    virtual void DoSomething(MyType&& a, const MyTypeB& b);
    virtual void DoSomething(const MyType& a, MyTypeB&& b);
    virtual void DoSomething(MyType&& a, MyTypeB&& b);
};

And the combinatorics get even worse if more parameters added to the method.

So in implementation i basically want to move argument if i got rvalue passed and copy otherwise.

There's std::forward in the standard library, but it works with so called "forwarding reference", which require templates and it's impossible to have template parameters in virtual methods.

Is there any way to do this with preserving the purpose of interface base type and without bloating the interface itself so much?

Hollah answered 20/2, 2022 at 17:33 Comment(12)
At work, for sink parameters, we'd use void DoSomething(MyType a, MyType b);. That way the caller can decide whether to move or copy, as appropriate for the callers needs. (Some of our methods have 6+ parameters, and having all permutations of MyType&& and MyType const& would be unmanageable.)Recipe
How do you expect "any of function arguments" to be an "rvalue or lvalue reference", when they cannot possibly be, because they're not declared as such? Both of the shown function parameters are const references. That's what they'll always be. The End.Woo
@Recipe But that function accepts arguments by value, so both values will be copies anywayHollah
"And the thing i want is to allow usage of move semantics if any of function arguments is rvalue or lvalue reference." To what end, exactly? What is this function doing that it needs to move from its parameters, but only sometimes?Incandesce
@NicolBolas To not make extra copy if arguments are rvalues and i, for example, want to put that values in a container. With rvalue references i can just move it to a container and don't make extra copy. If i accept all arguments as const lvalue references then there's only copy to container possibleHollah
@toozyfuzzy: Whether a function wants to claim ownership of a resource is generally part of what the function does. It's not optional or flexible; the function either claims the resources it is given or it doesn't. Forwarding references are for cases where you want to defer the decision to some unknown function. But a function is only "unknown" to templates; to regular code, what functions you call are known and thus their resource adoption behavior is a fixed part of their API. So it's not clear what the purpose of this API is.Incandesce
The arguments will be constructed but if the type has move semantics, it won't necessarily be copied. If you pass an rvalue to the function, it can move construct the argument, no copy needed.Valentijn
@toozyfuzzy: Put simply, why does DoSomething not know if it wants to move from a particular parameter or not? Why is that not simply part of what the function does, part of its inherent nature?Incandesce
@NicolBolas It wants only if it is rvalue. So i guess the best way to do so is to have move constructor for my type, accept everything by value and move that freshly constructed objects always to not overengineer the interface itselfHollah
@toozyfuzzy: "It wants only if it is rvalue." Why? What is the function doing such that it only sometimes wants to adopt the resource? Your example is too abstract and doesn't make sense.Incandesce
@NicolBolas For conveniance, sink functions that expect an rvalue reference often also want to provide an lvalue reference overload for when the user wants a copy. An example might be std::vector::push_back.Valentijn
But that function accepts arguments by value, so both values will be copies anyway. Partially correct. If the caller calls it mi.DoSomething(a, b); both will be call-by-copy, but if the caller calls it mi.DoSomething(move(a), move(b)); both will be call-by-move. Assuming MyType has both copy and move semantics.Recipe

© 2022 - 2024 — McMap. All rights reserved.