4

The following code compiles if and only if I remove Foo's custom destructor.

struct Foo {
    std::unique_ptr <int> bar;
    ~Foo (void) {} // This Line
};
std::vector <Foo> foos;
foos.push_back (Foo ());

Here is what I think I understand about the situation:

It fails because unique_ptrs cannot be copied, and std::vector::push_back (thing) calls the thing's copy constructor. If I write Foo a custom copy constructor which explicitly moves bar, then everything will be fine.

However, disabling This Line will cause the code to compile.

I thought that this should fail to compile even without This Line, because I'm still attempting to push_back a unique_ptr.

Why does this succeed without the custom destructor, and why does adding the custom destructor cause it to fail?

Edit: using gcc -std=gnu++11 on Debian Linux 64-bit

evenex_code
  • 843
  • 1
  • 8
  • 20

3 Answers3

7

I can't guarantee this is what is happening in your case, but it is related to something I've seen lately:

You can't copy unique pointers, but you can move them.

In C++11 you will get a default move constructor if you don't define a destructor, but if you define one the compiler doesn't have to provide the move constructor, in which case the code fails. (I know that Visual Studio won't do this, but on my Mac with Xcode I've still gotten the move constructor.)

So, I think this is what is happening in your case. Try provide the destructor and the other constructors/assignment operators to see if it fixes things. (See the discussions on the rule of five.)

Community
  • 1
  • 1
Nathan S.
  • 5,244
  • 3
  • 45
  • 55
  • It does, providing a copy constructor is enough to get the code compiling. I wasn't aware of the rule of five - this is very useful information. – evenex_code Mar 01 '14 at 07:29
  • Note that while providing a destructor suppresses the implicit definition of a *move* constructor, the compiler will still generate a *copy* constructor. Of course, the copy constructor will be "deleted", since the `unique_ptr` can't be copied. – Brian Bi Mar 01 '14 at 07:34
5

According to the standard "12.8.9 Copying and moving class objects [class.copy]"

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,
  • 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.

So if a class doesn't have user-defined destructor a move-constructor will be declared. And according "12.8.15" of the standard, this will call move-constructor for unique_ptr class member:

The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.

If a class has user-defined destructor, the move-constructor should be declared explicitly:

struct Foo {
         std::unique_ptr <int> bar;
 
         Foo() = default;
         Foo(Foo&&) = default;

         ~Foo (void) {} // This Line
 };
Community
  • 1
  • 1
fasked
  • 3,555
  • 1
  • 19
  • 36
  • Thanks for posting the line from the standard. I was studying this line yesterday, but didn't have it on hand to add to my answer. – Nathan S. Mar 01 '14 at 07:39
  • Declaring as `Foo(Foo&&) = default;` will suffice, you needn't actually implement the move constructor by hand. – Casey Mar 01 '14 at 20:09
1

Explicit destructor makes compiler not generate default move constructor and default assignement move operator, and those two are required by unique_ptr for ownership transfering. So you must implement those if you want to implement destructor:

Foo(){}
Foo &operator=(Foo &&o) {
    if (this != &o)
        bar = std::move(o.bar);
    return *this;
}
Foo(Foo &&o) : bar(std::move(o.bar)) {}

because Foo should not be copied, only moved I suggest also explicitly deleting copy constructor and assignment operator:

Foo(Foo const &) = delete;
Foo &operator=(Foo const &) = delete;  

[edit]

Actually, it is better explained by rule of five which can be easily found on google, if you implement any of the :

destructor
copy constructor
move constructor
copy assignment operator
move assignment operator

you should consider implementing all of them (or deleting), and this is especially true when using unique_ptr which makes use of move semantics.

When you comment out destructor, then you enter rule of zero.

marcinj
  • 48,511
  • 9
  • 79
  • 100