`noncopyable` with custom destructor - c++

I need a noncopyable class which has a declared destructor, and naive approach doesn't work: see https://ideone.com/mU8aoc. What's the problem with the destructor, why moving doesn't work the same way as without it? And of course, how to fix it?
For reference, the complete code (same as by the ideone link above):
class noncopyable {
public:
noncopyable(noncopyable &&) noexcept;
noncopyable &operator=(noncopyable &&) noexcept;
protected:
noncopyable() = default;
~noncopyable() = default;
noncopyable(const noncopyable &) = delete;
noncopyable &operator=(const noncopyable &) = delete;
};
class C: noncopyable {
public:
// compiles if this line is uncommented
// C(C&& c);
C() {}
// also compiles if this is commented
~C() {}
};
C a() {
return {};
}
C b() {
return a();
}
int main() {
return 0;
}

For your code to work, class C must be moveable. When it has no declared destructor, it gets a compiler-generated implicit move constructor (and move assignment operator). But when it has a declared ("custom" in your parlance) destructor, the move constructor (and move assignment operator) are no longer provided implicitly. This is for your safety: it is assumed that if you need an explicit destructor you will need explicit move functions as well.
Reference: http://en.cppreference.com/w/cpp/language/move_constructor

Related

C++ if a class cannot synthesize a move constructor, does it mean that the move constructor is deleted?

In code
class A
{
public:
A() = default;
A(const A& obj) = default;
};
since i define the copy constructor of A, A won't synthesize the move constructor. But i still can write the following code:
A obj;
A obj1 = std::move(obj); // call the copy constructor of A
But if i define class A like this
class A
{
public:
A() = default;
A(const A& obj) = default;
A(A&& obj) = delete;
};
the same code
A obj;
A obj1 = std::move(obj); // error: call to deleted constructor of A
then how to understand the difference between cannot synthesize a move constructor and the move constructor is deleted?

Why is a "=default" destructor different than the implicitly declared destructor?

So I read this post:
How is "=default" different from "{}" for default constructor and destructor?
Which discusses why:
~Widget() = default;
Isn't the same as:
~Widget() {}
However, it's also true that the "=default" case is different than the implicitly declared case. In some sense, =default doesn't actually give you the default, which is kinda odd.
Consider the following program:
class A
{
public:
A(std::string str)
{
m_str = str;
}
~A() = default;
A(A const& rhs)
{
printf("Got copied\n");
m_str = rhs.m_str;
}
A(A&& rhs)
{
printf("Got moved\n");
m_str = std::move(rhs.m_str);
}
std::string m_str;
};
class B
{
public:
B(std::string test) : m_a(test)
{
}
~B() = default;
A m_a;
};
int main()
{
B b("hello world");
B b2(std::move(b));
return 0;
}
Running this program will print "Got copied", unless you comment out the defaulted ~B() in which case it will print "Got moved". So why is this? I think "=default" is pretty confusing considering both this and the implicitly declared destructor are supposed to produce "trivial destructors".
The implicitly-defined move constructor for B only gets created if
there are no user-declared copy constructors;
there are no user-declared copy assignment operators;
there are no user-declared move assignment operators;
there are no user-declared destructors;
Now when you say ~B() = default;, while you still get the default destructor, it's now also considered user-declared, and thus there won't be an implicitly defined move constructor.

What do I Need to Return an Object with a unique_ptr Member?

Let's say that I have this object:
struct foo {
std::unique_ptr<int> mem;
virtual ~foo() = default;
};
I can no longer return a foo object created in a function:
foo make_foo() {
foo result;
result.mem = std::make_unique<int>({});
return result;
}
As may be indicated I need the destructor to be virtual because this will be a base class. But even if I'm using the default destructor, that's not enough, I still can't return an object created in a function.
I get the error:
error C2280: foo::foo(const foo &): attempting to reference a deleted function
Is there a way to navigate around this issue?
Per [class.copy.ctor]/8
If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if [...]
X does not have a user-declared destructor.
Since
virtual ~foo() = default;
is a user-declared destructor you no longer have a move constructor so it tries to use the copy constructor but can't because that is deleted as you have a non-copyable member.
To get the move constructor back, and to keep the default constructable, you need to add
foo() = default;
foo(foo&&) = default;
foo &operator=(foo &&) = default; // add this if you want to move assign as well
to foo
The reason you have to add foo() = default; when you add foo(foo&&) = default; is that foo(foo&&) = default; is a used-declared constructor and if you have any user-declared constructors then the default constructor is no longer provided.
This is a "hack" but what you could do is move the virtual destructor into another class and then inherit from that. That will give you a virtual destructor in foo without having to declare it and give you the default constructors you want. That would look like
struct make_virtual
{
virtual ~make_virtual() = default;
};
struct foo : make_virtual {
std::unique_ptr<int> mem;
};
Either provide your own copy constructor and a default constructor, convert the member to a shared pointer or provide a move constructor. One possible solution:
struct foo {
unique_ptr<int> mem;
foo() = default;
foo(const foo& copy);
virtual ~foo() = default;
};
foo::foo(const foo& copy) : mem(new int(*copy.mem)) {}
foo make_foo() {
foo result;
result.mem = make_unique<int>();
return result;
}

How to clone an object without copy constructor

According to CppCoreGuideline, I should disable the copy constructor of a base class and propose a clone method: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-copy-virtual
For example:
class B {
public:
explicit B() = default;
B(B&&) = default; // Move constructor
B& operator=(B&&) = default; // Move assignment operator
B(const B&) = delete; // Copy constructor
B& operator=(const B&) = delete; // Copy assignment
virtual ~B() = default;
virtual unique_ptr<B> clone()
{
return unique_ptr<B>{new B{*this}}; // how do this without copy constructor ?
}
private:
int c;
int d;
};
class D : public B {
public:
explicit D() = default;
D(D&&) = default; // Move constructor
D& operator=(D&&) = default; // Move assignment operator
D(const B&) = delete; // Copy constructor
D& operator=(const D&) = delete; // Copy assignment
virtual ~D() = default;
virtual unique_ptr<B> clone() override
{
// how can I copy all private data member of base class ???
}
};
but how can I copy all private data member in clone method? Obviously I'll use the CRTP pattern : C++: Deep copying a Base class pointer
I think the simplest way is to actually make the special members protected instead of deleted. This still prevents slicing, but makes it easier to implement clone(). Note that both the copy and move members need to be treated this way.
class B {
public:
// if this is truly intended to be a polymorphic base class, it probably
// doesn't make sense for the base to be able to clone itself.
virtual unique_ptr<B> clone() = 0;
protected:
B(B const& ) = default;
B& operator=(B const& ) = default;
private:
int c;
int d;
};
Which also allows the derived classes to do this easily:
class D : public B {
public:
D(D const& ) = default; // this is safe now
D& operator=(D const& ) = default;
unique_ptr<B> clone() override {
return unique_ptr<D>(new D(*this));
}
// ...
};
Rather than disabling the copy constructor, consider marking it protected. That way, clients of the class can't accidentally create a copy, but instances of the class can invoke the copy constructor as needed to implement the clone function. You can use the defaulted version of the copy constructor assuming you aren't doing any explicit resource management. Then, to implement clone, you can do something like this:
virtual unique_ptr<B> clone() override
{
return make_unique<D>(*this);
}
This invokes the object's own (protected) copy constructor, which in turn will invoke the base's (protected) copy constructor, etc.
As a note, there's no need to use CRTP here. Using good old fashioned copy constructors should be all you need.

Danger with virtual base move assignment operators when they are now allowed to be used?

This concerns the resolution of C++ Issue http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1402 . Summary:
template<typename T>
struct wrap
{
wrap() = default;
wrap(wrap&&) = default;
wrap(const wrap&) = default;
T t;
};
struct S {
S(){}
S(const S&){}
S(S&&){}
};
typedef wrap<const S> W;
// Error, defaulted move constructor of "wrap<const S>" is deleted!
W get() { return W(); }
(The issue is that we are getting an error for this snippet, even though the compiler could simply use the copy constructor of "S", as it does when the user explicitly writes the move constructor of "wrap". The issue was resolved to ignore deleted move constructors (and assignment operators) during overload resolutions, hence using the copy constructor above as desired.)
When the resolution was drafted, the following comment was made about this resolution:
There are no additional restrictions for implicit/defaulted move operations relative to copy. This means that move assignment in a virtual base is dangerous (and compilers should probably warn) [...] But we decided in Batavia that we weren't going to preserve all C++03 code against implicit move operations, and I think this resolution provides significant performance benefits.
Can someone please describe what the concern with virtual base move assignment operators is?
Consider:
#include <iostream>
struct A
{
A() = default;
A(const A&) = default;
A(A&&) = default;
A& operator=(const A&) {std::cout << "operator=(const A&)\n"; return *this;}
A& operator=(A&&) {std::cout << "operator=(A&&)\n"; return *this;}
};
struct B
: virtual public A
{
};
struct C
: virtual public A
{
};
struct D
: public B,
public C
{
};
int
main()
{
D d1, d2;
d1 = std::move(d2);
}
I believe this program should output:
operator=(A&&)
operator=(A&&)
For me it actually outputs:
operator=(const A&)
operator=(const A&)
But I think this is just a clang bug (compiled with -std=c++1y). If I am correct about what the output should be, then the danger is that the move assignment operator is called twice. This is harmless (though potentially expensive) with the copy assignment operator, but not with the move assignment operator.