问题
I have several classes for which I wish to check whether a default move constructor is being generated. Is there a way to check this (be it a compile-time assertion, or parsing the generated object files, or something else)?
Motivational example:
class MyStruct : public ComplicatedBaseClass {
std::vector<std::string> foo; // possibly huge
ComplicatedSubObject bar;
};
If any member of any base or member of either Complicated...Object
classes cannot be moved, MyStruct
will not have its implicit move constructor generated, and may thus fail to optimize away the work of copying foo
, when a move could be done, even though foo
is movable.
I wish to avoid:
- tediously checking the conditions for implicit move ctor generation,
- explicitly and recursively defaulting the special member functions of all affected classes, their bases, and their members—just to make sure a move constructor is available.
I have already tried the following and they do not work:
- use
std::move
explicitly—this will invoke the copy constructor if no move constructor is available. - use
std::is_move_constructible
—this will succeed when there is a copy constructor acceptingconst Type&
, which is generated by default (as long as the move constructor is not explicitly deleted, at least). - use
nm -C
to check the presence of move constructor (see below). However, an alternative approach is viable (see answer).
I tried looking at the generated symbols of a trivial class like this:
#include <utility>
struct MyStruct {
MyStruct(int x) : x(x) {}
//MyStruct(const MyStruct& rhs) : x(rhs.x) {}
//MyStruct(MyStruct&& rhs) : x(rhs.x) {}
int x;
};
int main() {
MyStruct s1(4);
MyStruct s2(s1);
MyStruct s3(std::move(s1));
return s1.x + s2.x + s3.x; // Make sure nothing is optimized away
}
The generated symbols looks like this:
$ CXXFLAGS="-std=gnu++11 -O0" make -B x; ./x; echo $?; nm -C x | grep MyStruct | cut -d' ' -f3,4,5
g++ -std=gnu++11 -O0 x.cc -o x
12
.pdata$_ZN8MyStructC1Ei
.pdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.text$_ZN8MyStructC1Ei
.text$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.xdata$_ZN8MyStructC1Ei
.xdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
MyStruct::MyStruct(int)
std::remove_reference<MyStruct&>::type&&
The output is the same when I explicitly default the copy and move constructors (no symbols).
With my own copy and move constructors, the output looks like this:
$ vim x.cc; CXXFLAGS="-std=gnu++11 -O0" make -B x; ./x; echo $?; nm -C x | grep MyStruct | cut -d' ' -f3,4,5
g++ -std=gnu++11 -O0 x.cc -o x
12
.pdata$_ZN8MyStructC1Ei
.pdata$_ZN8MyStructC1EOKS_
.pdata$_ZN8MyStructC1ERKS_
.pdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.text$_ZN8MyStructC1Ei
.text$_ZN8MyStructC1EOKS_
.text$_ZN8MyStructC1ERKS_
.text$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
.xdata$_ZN8MyStructC1Ei
.xdata$_ZN8MyStructC1EOKS_
.xdata$_ZN8MyStructC1ERKS_
.xdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_
MyStruct::MyStruct(int)
MyStruct::MyStruct(MyStruct&&)
MyStruct::MyStruct(MyStruct const&)
std::remove_reference<MyStruct&>::type&& std::move<MyStruct&>(MyStruct&)
So it appears this approach also doesn't work.
However if the target class has a member with explicit move constructor, the implicitly generated move constructor will be visible for the target class. I.e. with this code:
#include <utility>
struct Foobar {
Foobar() = default;
Foobar(const Foobar&) = default;
Foobar(Foobar&&) {}
};
struct MyStruct {
MyStruct(int x) : x(x) {}
int x;
Foobar f;
};
int main() {
MyStruct s1(4);
MyStruct s2(s1);
MyStruct s3(std::move(s1));
return s1.x + s2.x + s3.x; // Make sure nothing is optimized away
}
I will get the symbol for MyStruct
's move constructor, but not the copy constructor, as it appears to be fully implicit. I presume the compiler generates a trivial inlined move constructor if it can, and a non-trivial one if it must call other non-trivial move constructors. This still doesn't help me with my quest though.
回答1:
Declare the special member functions you want to exist in MyStruct
, but don't default the ones you want to check. Suppose you care about the move functions and also want to make sure that the move constructor is noexcept
:
struct MyStruct {
MyStruct() = default;
MyStruct(const MyStruct&) = default;
MyStruct(MyStruct&&) noexcept; // no = default; here
MyStruct& operator=(const MyStruct&) = default;
MyStruct& operator=(MyStruct&&); // or here
};
Then explicitly default them, outside the class definition:
inline MyStruct::MyStruct(MyStruct&&) noexcept = default;
inline MyStruct& MyStruct::operator=(MyStruct&&) = default;
This triggers a compile-time error if the defaulted function would be implicitly defined as deleted.
回答2:
As Yakk pointed out, it's often not relevant if it's compiler generated or not.
You can check if a type is trivial or nothrow move constructable
template< class T >
struct is_trivially_move_constructible;
template< class T >
struct is_nothrow_move_constructible;
http://en.cppreference.com/w/cpp/types/is_move_constructible
Limitation; it also permits trivial/nothrow copy construction.
回答3:
- disable inlining (
-fno-inline
) - either
- make sure a move constructor can be used by the code, or (better)
- temporarily add a call to
std::move(MyStruct)
anywhere in the compiled code to meet the odr-used requirement
- either
- make sure that
MyStruct
has at least one parent class or a non-static member (recursively), with a non-trivial move constructor (e.g. anstd::string
would suffice), or (easier) - temporarily add an std::string member to your class
- make sure that
- compile/link and run the resultant object file through
nm -C ... | grep 'MyStruct.*&&'
The result will imply whether the move constructor was generated or not.
As discussed in the question itself, this method didn't seem to work reliably, but after fixing the two issues that made it unreliable: inlining and triviality of the move constructor, it turned out to be a working method.
Whether the generated move constructor is implicitly or explicitly defaulted plays no role—whether the default is trivial or not is relevant: a trivial move (and copy) constructor will simply perform a byte-wise copy of the object.
来源:https://stackoverflow.com/questions/33939687/how-can-i-check-if-a-move-constructor-is-being-generated-implicitly