In the following code, a wrapper<T> object is declared which contains a movable<T>, where T is an incomplete type. The destructor of movable is made so that it cannot be instantiated without complete knowledge of T, but wrapper's destructor is only forward-declared, which means that it should be sufficient if ~movable() is instantiated at the point of definition of ~wrapper().
#include <utility>
template<class T>
struct movable {
movable() noexcept = default;
~movable() noexcept { (void) sizeof(T); }
movable(const movable&) noexcept = delete;
movable(movable &&) noexcept = default;
};
template<class T>
class wrapper {
public:
movable<T> m;
wrapper() noexcept = default;
wrapper(wrapper &&) noexcept = default;
~wrapper();
};
struct incomplete;
int main() {
/* extern */ wrapper<incomplete> original;
wrapper<incomplete> copy(std::move(original));
}
(Try it here)
However, wrapper() wants to instantiate ~movable(). I get that in case of an exception, destruction of members must be possible, but movable() and wrapper() are both noexcept. Interestingly, the move constructor works fine (try uncommenting the extern part in the example code.)
What is the reason for this behaviour, and is there a way to circumvent it?
As observed by T.C.,
In a non-delegating constructor, the destructor for [...] each non-static data member of class type is potentially invoked [...]
Per DR1424, the motivation is to make it clear that an implementation is required to issue an error if a destructor is inaccessible from the constructor of the parent object, "[even if] there is no possibility for an exception to be thrown following a given sub-object's construction".
The destructor of movable<T> is accessible, but it cannot be instantiated, which is where your problem arises as a potentially invoked destructor is odr-used.
This makes life simpler for the implementor, as they can just verify that each subobject has an accessible and if necessary instantiable destructor, and leave it to the optimizer to eliminate destructor calls that are not required. The alternative would be horribly complicated - a destructor would be required or not required depending on whether any succeeding subobjects were noexcept constructible, and on the constructor body.
The only way to avoid potential invocation of the destructor would be to use placement new, taking over management of the lifetime of the subobject yourself:
#include <new>
// ...
template<class T>
class wrapper {
public:
std::aligned_storage_t<sizeof(movable<T>), alignof(movable<T>)> m;
wrapper() noexcept { new (&m) movable<T>; };
wrapper(wrapper&& rhs) noexcept { new (&m) movable<T>{reinterpret_cast<movable<T>&&>(rhs.m)}; }
~wrapper();
};
Related
I have implemented a class reference<T> that keeps track of the amount of references to a T which derives from reference_countable.
I have a problem with it with respect to forward declaring the T of the reference<T>, similar to that of std::unique_ptr<T>. With std::unique_ptr<T> the issue comes from the destructor not beeing known, so all you have to do is put the destructor of your class into the cpp file, like so:
header:
class MyClass;
class A
{
std::unique_ptr<MyClass> my_class;
}
implementation:
A:~A() = default;
However, in my version, where std::unique_ptr<MyClass> is replaced by reference<MyClass>, the reference_countable must also be decremented and incremented. Which requires me to also put the copy-assignment and copy-constructor of A in cpp file when MyClass is only forward declared.
Is there a way to avoid having to put the implementation for these three functions in the cpp file for all classes with a reference<T> member?
I've tried to describe it as simple as I can, but for more detail, here is a simple version of the problem. Specifically, the need to define the copy-constructor of A in a.cpp.
my_class.h
#pragma once
#include "minimal_ref_counter.h"
class MyClass : public minimal_reference_countable
{
};
a.h
#pragma once
#include "minimal_ref_counter.h"
class MyClass;
class A
{
public:
A();
~A();
A(const A&);
minimal_reference_counter<MyClass> my_class;
};
a.cpp
#include "test.h"
#include "myclass.h"
A::A()
: my_class(new MyClass())
{}
A::~A() = default;
A::A(const A&) = default;
some_other_code.cpp
#include "a.h"
void some_function()
{
A a1;
A a2 = a1; // this code does not compile without the copy assignment operator beeing implemented externally.
}
minimal_ref_counter.h
#pragma once
#include <atomic>
class minimal_reference_countable
{
template<typename T>
friend class minimal_reference_counter;
std::atomic_int m_references = 0;
auto reference_count() const { return m_references.load(); }
void decrement() { --m_references; }
void increment() { ++m_references; }
};
template<typename T>
class minimal_reference_counter
{
public:
minimal_reference_counter(T* t = nullptr)
{
assign(t);
}
~minimal_reference_counter()
{
reset();
}
minimal_reference_counter(const minimal_reference_counter& r)
{
*this = r;
}
minimal_reference_counter(minimal_reference_counter&& r)
{
*this = std::move(r);
}
minimal_reference_counter& operator=(const minimal_reference_counter& r)
{
assign(r.m_ptr);
return *this;
}
minimal_reference_counter& operator=(minimal_reference_counter&& r)
{
assign(r.m_ptr);
r.reset();
return *this;
}
void reset()
{
if (!m_ptr) return;
m_ptr->decrement();
if (m_ptr->reference_count() == 0)
{
delete m_ptr;
}
m_ptr = nullptr;
}
private:
void assign(T* ptr)
{
reset();
m_ptr = ptr;
if (m_ptr) m_ptr->increment();
}
T* m_ptr = nullptr;
};
C++ templates are lazy. Instantiations are deferred until necessary. The reason to put the destructor in the cpp file when you have a member std::unique_ptr of an incomplete type, later completed, is that the destructor needs the destructor of unique_ptr, thus instantiating it, which needs the destructor of the pointed-to type which requires that type to be complete. This chain of dependencies triggered by the definition of the destructor must be deferred until such time as the pointed-to type is complete.
In your reference counted pointer case, you wonder if the copy constructor, copy assignment operator, and destructor of the pointer necessarily require completeness of the pointed-to type, and likewise their instantiations be deferred until such time as the pointed to type is complete.
Because the pointer destructor potentially invokes the destructor of the pointed-to type, that type necessarily must be complete when the pointer destructor is instantiated. This is similar to unique_ptr and so should be unsurprising.
Similarly, the pointer copy assignment potentially invokes the destructor of the pointed-to type. It replaces the pointee with another pointee and decrements the original pointee's reference count and destroys it if necessary. So, for the same reason as the destructor, the pointer copy assignment operator requires the pointed-to type necessarily be complete when it is instantiated.
The copy constructor is more subtle. No potential destructions of the pointed-to type are invoked. However, with this implementation, we act on a base class of the pointed-to type to increment the reference count. This requires completeness, as otherwise there are no base classes. So, with this implementation, yes, the poitned-to type necessarily must be complete when the copy constructor is instantiated.
There are alternative implementations, of course. One could keep both a T* and a minimal_reference_counter<T>*, instead of finding the latter from the former as a base class. shared_ptr does this. In fact, one could also store the destructor as a function pointer and not require the other awkward instantiation deferrals. shared_ptr does this too.
How can I overcome/workaround this error in g++-6.2.1
The following code works with g++-7.3.0 but upgrading the compiler is not a option for me. So I am looking for some SFINAE magics... trying few but failed so far...
class Base {
public:
Base(std::string str) : s(std::make_shared<std::string>(str)) {}
Base(Base &&base) noexcept { s = std::move(base.s); }
Base &operator=(Base &&i_base_actor) noexcept {
s = std::move(i_base_actor.s);
return *this;
}
virtual ~Base() = default;
private:
std::shared_ptr<std::string> s;
};
// Derived
class Derived : public Base {
public:
Derived() :Base("Derived") {}
~Derived() = default;
};
// Derived1
class Derived1 : public Base {
public:
Derived1(int a) :Base("Derived1") {}
~Derived1() = default;
};
Wrapper function:
template<typename T, typename... Args>
T construct(Args&&... args) {
return T(std::forward<Args>(args)...);
}
Main:
int main() {
construct<Derived>();
construct<Derived1>(100);
}
Error in g++
optional_params.derived.cc: In instantiation of ‘T construct(Args&& ...) [with T = Derived; Args = {}]’:
optional_params.derived.cc:42:22: required from here
optional_params.derived.cc:37:19: error: use of deleted function ‘Derived::Derived(const Derived&)’
return T(args...);
^
optional_params.derived.cc:21:7: note: ‘Derived::Derived(const Derived&)’ is implicitly deleted because the default definition would be ill-formed:
class Derived : public Base {
^~~~~~~
optional_params.derived.cc:21:7: error: use of deleted function ‘Base::Base(const Base&)’
optional_params.derived.cc:4:7: note: ‘Base::Base(const Base&)’ is implicitly declared as deleted because ‘Base’ declares a move constructor or move assignment operator
class Base {
^~~~
Your code relies on guaranteed copy elision C++17 on the following line:
template<typename T, typename... Args>
T construct(Args&&... args) {
return T(std::forward<Args>(args)...); // <----- copy elison
}
Basically, it says that as of C++17, the compiler must not copy T in this case, and is required to construct it directly in the caller. At C++14 and earlier, the compiler had to make sure that the move (or copy) constructor is accessible, even in cases when it optimized away the copy constructor. Apparently, gcc-6.2.1 did not support this aspect of C++17, even with the -std=c++17 flag.
The simplest way out is to add a move constructor to the derived class:
Derived(Derived &&) noexcept = default;
This way, the C++14 compiler sees that there is a way to return a value even in a hypothetical case when copy elision is not performed. Note that any reasonable C++14 compiler will perform copy elision, but it will still make sure that either copy or move constructors are accessible. As of C++17 no such test is performed, since the compiler must elide the copy/move in this case.
As mentioned in the comment section, another possibility is:
template<typename T, typename... Args>
T construct(Args&&... args) {
return {std::forward<Args>(args)...};
}
which will also construct it directly in the caller, but only if T's constructor is not explicit.
Alternatively, another comment suggests avoiding the explicit destructor. The explicit destructor inhibits the auto-generation of default move-constructors:
class Derived : public Base {
public:
Derived() :Base("Derived") {}
//~Derived() = default; <-- not really needed.
};
But, since this is only a minimal reproducible example, it is possible that in the complete code the explicit destructor is in fact required. In that case, avoiding the destructor is not an option.
I have an std::multimap of the following type:
typedef std::multimap<std::pair<bool,uint32_t>, FooObject>
The FooObject has default move copy and assign constructors declared:
FooObject(FooObject&& v) = default;
FooObject& operator=(FooObject&& other)=default;
The copy/assign constructors are private to disable implicit copy.
So I should be able to emplace a pair into the map like this:
mymap.emplace(std::make_pair(false,32),FooObject());
This throws a list of errors with the one at the end:
error C2660: 'std::pair::pair': function does not take
2 arguments
If I declare move copy assign constructors without "default"
then it compiles ok.
FooObject(FooObject&& v){}
FooObject& operator=(FooObject&& other){}
Why is that? Does the compiler optimize away these constructors when marked with "default" keyword? I am using MSVC140
UPDATE:
Based on the comments below I found the reason - FooObject has a non-copiable member instance.
Here is the FooObject:
#define NO_COPY_ASSIGN(TypeName) \
TypeName (const TypeName &); \
void operator= (const TypeName &);
class FooObject
{
private:
NO_COPY_ASSIGN(FooObject)
public:
struct FooStruct
{
FooBuffer frameBuffer; //<--Here it is
}fooStruct;
FooObject(){}
/** Move constructor to allow insert into vector without copy */
FooObject(FooObject&& v) = default;
FooObject& operator=(FooObject&& other) = default;
};
*FooBuffer has also its copy/assign private.But I still don't get why replacing 'default' with {} fixes that.Please explain.
Your problem is that one of the member of FooObject is not move-able, which prevents the compiler from generating default move operations.
The {} versions of the move operations you implement yourself do no work (specifically: they don't actually do a move operation) on the members of FooObject and are thus legal.
The difference between
FooObject(FooObject&& v) = default;
and
FooObject(FooObject&& v){}
Is that the former will emit a constructor that moves each member from v while the latter default constructs each member and does nothing with v.
Since FooBuffer is not movable that means that the compiler will delete FooObject(FooObject&& v) = default; as it would be ill-formed.
With FooObject(FooObject&& v){} you do not have that problem as you never try to move the members of v. Since there is no member initialization list the compiler will add one for you that just default constructs the members.
You can see this behavior more explicitly with this:
struct Moveable
{
Moveable() = default;
Moveable(Moveable&&) { std::cout << "in Moveable(Moveable&&)\n"; }
};
struct Foo
{
Foo() = default;
Foo(Foo&&) = default;
Moveable m;
};
struct Bar
{
Bar() = default;
Bar(Bar&&){}
Moveable m;
};
int main()
{
Foo f;
Bar b;
std::cout << "test_f\n";
Foo test_f(std::move(f));
std::cout << "test_b\n";
Bar test_b(std::move(b));
}
which outputs
test_f
in Moveable(Moveable&&)
test_b
Live Example
Showing that nothing is actually moved in Bar's move constructor.
Take a look a the following code example which uses class uncopiable similar to boost::noncopyable:
#include <vector>
class uncopiable {
using self = uncopiable;
protected:
uncopiable() {}
~uncopiable() {}
uncopiable(const self&) = delete;
self& operator=(const self&) = delete;
};
struct A {
struct B : uncopiable {
using self = B;
B() {
}
B(B&&) = default;
self& operator=(B&&) = default;
~B() {
}
};
A() { v.emplace_back(); }
~A() {}
private:
std::vector<B> v;
};
int main () {}
Since I wanted to make inner class move only I explicitly specified its move constructor and assignment operator to be default ones but also since I've heard that it's a good practice to specify all of the "special member functions" in such case I inherited it from uncopiable. The problem is that compilation fails with every compiler and something similar to the following error message is displayed (this message is excerpt from the clang one):
/usr/include/c++/v1/memory:1645:31: error: call to implicitly-deleted copy constructor of 'A::B'
...
main.cpp:26:10: note: in instantiation of function template specialization 'std::__1::vector >::emplace_back<>' requested here
main.cpp:19:3: note: copy constructor is implicitly deleted because 'B' has a user-declared move constructor
It could be fixed by removing inheritance (copy operations would still not be created). But writing copy operations to be explicitly deleted inside class after that is also okay.
My questions are: why does it happen? Could it be considered a deficiency of disabling constructors/assignment operators through inheritance of helper classes?
The problem is that your uncopiable class is not moveable. Therefore the default move constructor / assignment operator of the derived class try to use the deleted copy versions.
static_assert(std::is_move_constructible<uncopiable>::value, ""); // fails
static_assert(std::is_move_assignable<uncopiable>::value, ""); // fails
The reason for this is § 12.8 ¶ 9:
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, and
X does not have a user-declared destructor.
Declaring a copy operator or assignment operator as deleted still counts as declaring it.
The solution is of course to declare the move operations for uncopiable.
uncopiable(uncopiable&&) noexcept = default;
uncopiable& operator=(uncopiable&&) noexcept = default;
Note that the move operations should usually be declared noexcept. Especially if you want to use the type in a std::vector like in your example.
This compiles ok on MinGw:
#include <vector>
class uncopiable {
using self = uncopiable;
protected:
uncopiable() {}
~uncopiable() {}
uncopiable(const self&) = delete;
self& operator=(const self&) = delete;
};
struct A {
struct B : uncopiable {
using self = B;
B() {
}
B(B&&) {};
self& operator=(B&&) = default;
~B() {
}
};
A() { v.emplace_back(); }
~A() {}
private:
std::vector<B> v;
};
int main () {
A* a = new A();
}
I'm busy testing an implementation of various generic algorithms and I'm using types with minimal support of provided functions. I came across this weird setup when using a std::pair<T, movable> with some type T (e.g., int) and a movable type defined like this:
struct movable
{
movable() {}
movable(movable&&) = default;
// movable(movable const&) = delete;
movable(movable&) = delete;
};
The idea is have a type which is movable but not copyable. That works great, e.g., with expressions like this:
movable m1 = movable();
movable m2 = std::move(m1);
However, when trying to use this type as a member of std::pair<...> it fails! To make get the code to compile it is necessary to add the deleted(!) copy constructor taking a movable const& (or have only that version). The copy constructor taking a non-const reference is insufficient:
#include <utility>
auto f() -> std::pair<int, movable> {
return std::pair<int, movable>(int(), movable());
}
What is going on here? Is std::pair<...> overspecified by mandating that std::pair(std::pair const&) is = defaulted?
The problem seems to be down to the specification of std::pair's copy constructor (in 20.3.2 [pairs.pair] synopsis):
namespace std {
template <class T1, class T2>
struct pair {
...
pair(const pair&) = default;
...
};
}
A quick check with my implementation implies that the obvious implementation copying the two members does not require the const& version of the movable copy constructor. That is, the offensive part is the = default on pair's copy constructor!
std::pair copy constructor is declared as follows:
pair(const pair&) = default;
By declaring this copy constructor for movable:
movable(movable&) = delete;
you inhibit implicit creation of movable(const movable&) (so it's not even deleted, there's just no such constructor), thus this is the only copy constructor you have. But std::pair copy constructor requires a copy constructor of its members to take const reference, so you get compile error.
If you add this:
movable(movable const&) = delete;
or (better) just remove movable(movable&) = delete; declaration, you now have the movable(movable const&) constructor, and because it's deleted, the std::pair copy constructor also becomes deleted.
Update: Let's consider a simpler example demonstrating the same issue. This doesn't compile:
template <typename T>
struct holder {
T t;
// will compile if you comment the next line
holder(holder const&) = default;
// adding or removing move constructor changes nothing WRT compile errors
// holder(holder&&) = default;
};
struct movable {
movable() {}
movable(movable&&) = default;
// will also compile if you uncomment the next line
//movable(movable const&) = delete;
movable(movable&) = delete;
};
holder<movable> h{movable()};
It will compile if you comment the copy constructor of holder, because this is how implicit copy constructor generation works ([class.copy]/8:
The implicitly-declared copy constructor for a class X will have the form
X::X(const X&)
if each potentially constructed subobject of a class type M (or array thereof) has a copy constructor whose first parameter is of type const M& or const volatile M&. Otherwise, the implicitly-declared copy constructor will have the form
X::X(X&)
That is, when you comment out the declaration holder(holder const&) = default; the implicitly declared copy constructor of holder will have the form holder(holder&). But if you don't, T's copy constructor has take const T& (or const volatile T&) because this is what will be called in memberwise copy procedure described in [class.copy]/15.
And if holder has a move constructor, it's even easier - if you comment out holder(holder const&) = default;, the implicitly declared copy constructor of holder will be just deleted.