简体   繁体   中英

std::move in constructor initializer list in class template

I have a template like this:

template<typename T>
struct foo {
  T m_t;
  foo(T t) : m_t(t) {}
};

The problem is that I want to support both small/regular types and huge types (like matrices) for T . Do you recommend I write the constructor initializer list like this

foo (T t) : m_t(std::move(t)) {}

and require that the type T always support move construction, even for smaller types? Are there better ways?

and require that the type T always support move construction, even for smaller types?

Any type that is copy constructable is also move constructable. Moving in those cases simply calls the copy constructor. Thus, there's no reason not to use m_t(std::move(t)) .

An alternative is to use references instead:

foo (T const& t) : m_t(t) {}
foo (T&& t) : m_t(std::move(t)) {}

This has the benefit of only involving one construction rather than two.

Yes, using the move has no disadvantage in that situation. All copyable objects are automatically moveable, so it doesn't matter. In fact, some recommend to always move variables when possible, even integers.

As an alternative, you may consider to use perfect forwarding, as described in this answer :

template <typename T2>
foo(T2&& t) : m_t(std::forward<T2>(t)) {}

If you know that T defines a fast move constructor, it should not matter. Otherwise, providing a constructor foo(const T&) is recommended to avoid unnecessary copies.

Perfect forwarding is only one technique to achieve that. The solution of Pubby to write out the constructor foo(const T&) and foo(T&&) is, of course, also fine. The results are the same, it is mostly a matter of style.

You also asked about small integer types. In theory, passing them by reference is slower than copying them, but the compiler should be able to optimize it to a copy, anyway. I don't think it will make a difference.

So, better optimize for the worst case where T can be huge and does not provide a fast move constructor. Passing by reference is best for that situation and should not be a bad choice in general, either.

Passing by value has the advantage of only having a single constructor (without template), but comes at the price of one additional move construction compared to the aforementioned alternatives.

However, there is another yet unmentioned drawback of both pass-by-value and the non-template solution given by Pubby: Moving won't work if the copy constructor is defined as T(T&); (note the reference to non-const). Rvalue-references can bind to lvalue-references-to-const, but not to lvalue-references-to-non-const, so the compiler can't call the copy constructor.

To resolve this issue, you can simply add a third overload foo (T & t) : m_t(t) {} to Pubby's solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM