Default Move Constructor in Visual Studio 2013 (Update 3)

Default Move Constructor in Visual Studio 2013 (Update 3)

I know that you cannot mark move constructors as default, but that does not imply that the compiler does not support generating default move constructors all-together

Unfortunately, that's exactly what that means. VS2013 does not support implicit generation of move constructors and move assignment operators. If it did, they wouldn't really have a reason to disallow the = default syntax, especially since you're allowed to do that for the copy constructor and assignment operator.

Quoting MSDN: Support For C++11 Features (Modern C++)

"Rvalue references v3.0" adds new rules to automatically generate move constructors and move assignment operators under certain conditions. However, this is not implemented in Visual C++ in Visual Studio 2013, due to time and resource constraints.

std::move(std::array) g++ vs visual-c++

This is a bug in MSVS 2013. MSVS 2013 does not generate implicit move constructors. If you run it in a MSVS 2015 or 2017 you get the same output.


I would also like to point out that

B(array<A, 2>& a) : m_a(std::move(a))

Is not how you want to move an object into B. If you want B to take over the array you should have

B(array<A, 2>&& a) : m_a(std::move(a))

This means that instead of using

B b(a);

you have to use

B b(std::move(a));

and now you can clearly see that a has been moved from in main.

Why no default move-assignment/move-constructor?

The implicit generation of move constructors and assignment operators has been contentious and there have been major revisions in recent drafts of the C++ Standard, so currently available compilers will likely behave differently with respect to implicit generation.

For more about the history of the issue, see the 2010 WG21 papers list and search for "mov"

The current specification (N3225, from November) states (N3225 12.8/8):

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor, and

  • X does not have a user-declared copy assignment operator,

  • X does not have a user-declared move assignment operator,

  • X does not have a user-declared destructor, and

  • the move constructor would not be implicitly defined as deleted.

There is similar language in 12.8/22 specifying when the move assignment operator is implicitly declared as defaulted. You can find the complete list of changes made to support the current specification of implicit move generation in N3203: Tightening the conditions for generating implicit moves
, which was based largely on one of the resolutions proposed by Bjarne Stroustrup's paper N3201: Moving right along.

How to implement move constructor for large size non-pointer member?

A conforming C++11 compiler will automatically create a move constructor for you (Visual Studio 2013 does not). The default move constructor will do a member-wise move and std::vector implements move-semantics so it should do what you want.

But yes, if for some reason you do need to define the move constructor yourself that looks correct. Although it is preferable to use the constructor initialization list and declare it noexcept:

MemoryPage::MemoryPage(MemoryPage &&other) noexcept : buff(std::move(other.buff)) { }

When is the compiler allowed to optimize auto+brace style initialization?

the standard is very clear as you mentioned before, indicating that this is a bug in the cl-compiler. You never can be sure, though if one compiler is saying something and all others disagree, I expect that this would be one of the many non-standard-compliant implementations of the MSVC compiler.

The interpretation of clang version 3.7 (svn-build):

t.cpp:19:7:{19:11-19:30}: error: call to deleted constructor of 'Product'
[Semantic Issue]
auto p = Product{"abc", 123};
^ ~~~~~~~~~~~~~~~~~~~
t.cpp:8:2: note: 'Product' has been explicitly marked deleted here
[Semantic Issue]
Product(Product &&rhs) = delete;
^
1 error generated.
make: *** [t.o] Error 1

The interpretation of gcc 4.8:

t.cpp: In function ‘int main()’:
t.cpp:19:29: error: use of deleted function ‘Product::Product(Product&&)’
auto p = Product{"abc", 123};
^
t.cpp:8:2: error: declared here
Product(Product &&rhs) = delete;
^
make: *** [build/gcc/t.o] Error 1

Keep also in mind that the Explicitly Defaulted and Deleted Functions are new since MSVC 2013 and the implementation of it is not yet complete. Like it does not yet understand =default for move constructors.

My guess would be that MSVC 2013 does not check for the move constructor or simply falls back to the copy constructor.

It might be interesting to check MSVC 2015, since it appears to have a (more) complete implementation of these constructions.

JVApen

Do we need to set move constructor = default? For legacy classes with user declared destructor buildet with C++98/03 in the past?

Do you think there will be a performance benefit by defining the move constructor = default?

It's not possible to predict that without studying the code base and running some benchmarks. That being sad, it is likely to positively affect your runtime performance. Having a = default move constructor and move assignment operator and given that these do the right thing is never a pessimisation. The worst thing that can happen is that your data member don't benefit from move-construction (e.g. you have a std::array<int, 1000> data member), then you end up doing the same copying as before.

And the project followed the rule of three

This leads to a consistency issue when you modernize your code. In C++11, the rule of three now is the rule of five. So if you followed the rule of three until now, you should migrate to the rule of five to be consistent, in addition to the runtime performance benefit you might get.

is it a good idea to set the move constructor = default manually?

No. Use clang-tidy's C.21 check to let a tool scream at you.

Correct way to write move constructor with unique_ptr member (crash)

This sounds like a VS-2013 bug. But it also looks like your code, though well-formed, is probably not doing what you want it too (however only you can say that for sure).

I added a print statement to your Foo:

class Foo
{
public:
Foo(std::unique_ptr<A> ref) : mRef(std::move(ref)) {}
Foo(Foo&& other) : mRef(std::move(other.mRef)) {}

Foo(const Foo& other) {}
Foo& operator=(const Foo& other) { return *this; }

friend std::ostream&
operator<<(std::ostream& os, const Foo& f)
{
if (f.mRef)
os << *f.mRef;
else
os << "nullptr";
return os;
}

protected:
std::unique_ptr<A> mRef;
};

And I added a print statement to your main as well:


aside: I also added identity/state to your A so that it was easier to see what is going on.


int main(int argc, char *argv[])
{
std::vector<Foo> v({ Foo(std::make_unique<A>(1)), Foo(std::make_unique<A>(2)) });
// Crash : Debug Assertion Failed !
// Expression : _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)
for (const auto& p : v)
std::cout << p << '\n';
}

For me this outputs:

nullptr
nullptr

which I believe is the correct output.

One can not move from an initializer_list, and thus the vector constructor invokes Foo's copy constructor which just default constructs unique_ptr.

Indeed if one removes the Foo copy constructor, which should be implicitly deleted then (or you could explicitly delete it), the program should not compile.

To really make this work, you have to give Foo an operational copy constructor. Perhaps something like this:

    Foo(const Foo& other)
: mRef(other.mRef ? new A(*other.mRef) : nullptr)
{}

So in summary, I think both the compiler and the current code get awards for bugs. Though from the comments, it sounds like the current code bug is simply an artifact of correctly reducing the code to isolate the problem.

VS-2013 bug.

As for your move constructor, it is fine. Though it would be even better if implemented with = default. If you do so, it will automatically inherit a noexcept spec. Such a spec should not be taken lightly. It is most important for efficient use of vector<Foo>.

My understanding is that VS-2013 does not understand either defaulted move members nor noexcept.

My anecdotal experience with VS-2013 is that the number of compiler bugs one experiences is directly proportional to the use of curly braces. I am hopeful that VS-2015 will contradict this experience. In the meantime I recommend avoiding using construction expressions that involve {}.


Update

Your updated copy members in:

class Foo
{
public:
Foo(std::unique_ptr<A> ref) : mRef(std::move(ref)) {}
Foo(Foo&& other) : mRef(std::move(other.mRef)) {}

Foo(const Foo& other) : mRef(other.mRef->clone()) {}
Foo& operator=(const Foo& other) { mRef = other.mRef->clone(); return *this; }

protected:
std::unique_ptr<A> mRef;
};

have potential nullptr-dereference bugs. If other is in a moved-from state, the ->clone() is going to dereference a nullptr. Technically you can get away with this if you are very, very careful. However it will be very easy for you to accidentally hit this bug.

C++ Compiler Error C2280 attempting to reference a deleted function in Visual Studio 2013 and 2015

From [class.copy]/7, emphasis mine:

If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly.
If the class definition declares a move constructor or move assignment operator, the implicitly declared copy
constructor is defined as deleted
; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if
the class has a user-declared copy assignment operator or a user-declared destructor.

There is an equivalent section with similar wording for copy assignment in paragraph 18. So your class is really:

class A
{
public:
// explicit
A(){}
A(A &&){}

// implicit
A(const A&) = delete;
A& operator=(const A&) = delete;
};

which is why you can't copy-construct it. If you provide a move constructor/assignment, and you still want the class to be copyable, you will have to explicitly provide those special member functions:

    A(const A&) = default;
A& operator=(const A&) = default;

You will also need to declare a move assignment operator. If you really have a need for these special functions, you will also probably need the destructor. See Rule of Five.



Related Topics



Leave a reply



Submit