问题
I want to write a template function that receives parameter by move or by copy. The most efficient way that I use is:
void setA(A a)
{
m_a = std::move(a);
}
Here, when we use is
A a;
setA(a); // <<---- one copy ctor & one move ctor
setA(std::move(a)); // <<---- two move ctors
I recently found out that defining it this way, with two functions:
void setA(A&& a)
{
m_a = std::move(a);
}
void setA(const A& a)
{
m_a = a; // of course we can so "m_a = std::move(a);" too, since it will do nothing
}
Will save a lot!
A a;
setA(a); // <<---- one copy ctor
setA(std::move(a)); // <<---- one move ctor
This is great! for one parameter... what is the best way to create a function with 10 parameters?!
void setAAndBAndCAndDAndEAndF...()
Any one has any ideas? Thanks!
回答1:
The best would be to construct a
in-place within the constructor. About setters, there is no single best. Taking by value and moving seems to work fine in most cases, but can sometimes be less efficient. Overloading as you showed is maximally efficient, but causes lots of code duplication. templates can avoid code duplication with the help of universal-references, but then you have to roll out your own type checking and it gets complicated. Unless you've detected this as a bottleneck with a profiler, I suggest you stick with take-by-value-then-move as it's the simplest, causes minimal code duplication and provides good exception-safety.
回答2:
The two setter versions setA(A&& a)
and setA(const A& a)
can be combined into a single one using a forwarding reference (a.k.a. perfect forwarding):
template<typename A>
void setA(A&& a)
{
m_a = std::forward<A>(a);
}
The compiler will then synthesize either the rvalue- or lvalue-reference version as needed depending on the value category.
This also solves the issue of multi-value setters, as the right one will be synthesized depending on the value category of each parameter.
Having said that, keep in mind that setters are just regular functions; the object is technically already constructed by the time any setter can be called. In case of setA
, if A
has a non-trivial constructor, then an instance m_a
would already have been (default-)constructed and setA
would actually have to overwrite it.
That's why in modern C++, the focus is often not so much on move- vs. copy-, but on in-place construction vs. move/copy.
For example:
struct A {
A(int x) : m_x(x) {}
int m_x;
};
struct B {
template<typename T>
B(T&& a) : m_a(std::forward<T>(a)) {}
A m_a;
};
int main() {
B b{ 1 }; // zero copies/moves
}
The standard library also often offers "emplace"-style calls in addition to more traditional "push"/"add"-style calls. For example, vector::emplace takes the arguments needed to construct an element, and constructs one inside the vector, without having to copy or move anything.
回答3:
Short answer:
It's a compromise between verbosity and speed. Speed is not everything.
defining it this way, with two functions ... Will save a lot!
It will save a single move-assignment, which often isn't a lot.
Unless you need this specific piece of code to be as fast as possible (e.g. you're writing a custom container), I'd prefer passing by value because it's less verbose.
Other possible approaches are:
Using a forwarding reference, as suggested in the other answers. It'll give you the same amount of copies/moves as a pair of overloads (
const T &
+T &&
), but it makes passing more than one parameter easier, because you only have to write a single function instead of 2N of them.Making the setter behave like emplace(). This will give you no performance benefit (because you're assigning to an existing object instead of creating a new one), so it doesn't make much sense.
回答4:
After a lot of research, I have found an answer!
I made an efficient wrapper class that allows you to hold both options and lets you decide in the inner function whether you want to copy or not!
#pragma pack(push, 1)
template<class T>
class CopyOrMove{
public:
CopyOrMove(T&&t):m_move(&t),m_isMove(true){}
CopyOrMove(const T&t):m_reference(&t),m_isMove(false){}
bool hasInstance()const{ return m_isMove; }
const T& getConstReference() const {
return *m_reference;
}
T extract() && {
if (hasInstance())
return std::move(*m_move);
else
return *m_reference;
}
void fastExtract(T* out) && {
if (hasInstance())
*out = std::move(*m_move);
else
*out = *m_reference;
}
private:
union
{
T* m_move;
const T* m_reference;
};
bool m_isMove;
};
#pragma pack(pop)
Now you can have the function:
void setAAndBAndCAndDAndEAndF(CopyOrMove<A> a, CopyOrMove<B> b, CopyOrMove<C> c, CopyOrMove<D> d, CopyOrMove<E> e, CopyOrMove<F> f)
With zero code duplication! And no redundant copy or move!
来源:https://stackoverflow.com/questions/64498992/best-way-to-create-a-setter-function-in-c