Подтвердить что ты не робот

Как я могу проверить, создается ли конструктор перемещения неявно?

У меня есть несколько классов, для которых я хочу проверить, генерируется ли конструктор перемещения по умолчанию. Есть ли способ проверить это (будь то утверждение времени компиляции или синтаксический анализ созданных объектных файлов или что-то еще)?


Мотивационный пример:

class MyStruct : public ComplicatedBaseClass {
    std::vector<std::string> foo; // possibly huge
    ComplicatedSubObject bar;
};

Если какой-либо член любой базы или члена любого из классов Complicated...Object не может быть перемещен, MyStruct не будет иметь сгенерированный им неявный конструктор перемещения и, таким образом, не сможет оптимизировать работу копирования foo, когда перемещение может быть выполнено, хотя foo является подвижным.


Я хочу избежать:

  • утомительно проверяя условия для неявного перемещения ctor,
  • явно и рекурсивно дефолтировать специальные функции-члены всех затронутых классов, их баз и их членов. Просто убедитесь, что доступен конструктор перемещения.

Я уже пробовал следующее, и они не работают:

  • используйте std::move явно:— это вызовет копию ctor, если нет перемещения ctor.
  • use std::is_move_constructible — это будет успешным, если есть конструктор копирования, принимающий const Type&, который генерируется по умолчанию (если конструктор перемещения явно не удален, по крайней мере,).
  • используйте nm -C, чтобы проверить наличие перемещения ctor [см. ниже]. Однако альтернативный подход является жизнеспособным [см. Ответ].

Я попытался просмотреть созданные символы тривиального класса следующим образом:

#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
}

Сгенерированные символы выглядят следующим образом:

$ 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&&

Результат тот же, когда я явно по умолчанию копирует и перемещает ctors (без символов).

С моей собственной копией и перемещением ctors вывод выглядит следующим образом:

$ 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&)

Итак, похоже, этот подход также не работает.


Однако, если у целевого класса есть член с явным конструктором перемещения, неявно созданный конструктор перемещения будет виден для целевого класса. То есть с этим кодом:

#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
}

Я получу символ для MyStruct move ctor, но не копию ctor, поскольку он кажется полностью неявным. Я предполагаю, что компилятор генерирует тривиальное вложенное перемещение ctor, если это возможно, и нетривиальное, если он должен вызывать другие нетривиальные перемещения. Это все равно не помогает мне в моих поисках.

4b9b3361

Ответ 1

Объявите специальные функции-члены, которые вы хотите использовать в MyStruct, но не по умолчанию те, которые вы хотите проверить. Предположим, вы заботитесь о функциях перемещения, а также хотите, чтобы конструктор перемещения 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
};

Затем явно по умолчанию они выходят за пределы определения класса:

inline MyStruct::MyStruct(MyStruct&&) noexcept = default;
inline MyStruct& MyStruct::operator=(MyStruct&&) = default;

Это вызывает ошибку времени компиляции, если функция по умолчанию будет неявно определена как удаленная.

Ответ 2

Как отметил Якк, это часто не актуально, если он сгенерирован компилятором или нет.

Вы можете проверить, что, если тип тривиален или нет, но конструктивный конструктор

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

Ограничение; он также позволяет создавать тривиальные/нестровые копии.

Ответ 3

  • отключить inlining (-fno-inline)
  • либо
    • убедитесь, что конструктор перемещения может использоваться кодом или (лучше)
    • временно добавить вызов std::move(MyStruct) в любом месте скомпилированного кода для удовлетворения odr-used require
  • либо
    • убедитесь, что MyStruct имеет хотя бы один родительский класс или нестатический член (рекурсивно), с нетривиальным конструктором перемещения (например, a std::string) или (проще)
    • временно добавить член std::string в ваш класс
  • выполнить компиляцию/ссылку и запустить результирующий файл объекта через nm -C ... | grep 'MyStruct.*&&'

Результат будет означать, был ли создан конструктор перемещения или нет.


Как обсуждалось в самом вопросе, этот метод, похоже, не работал надежно, но после устранения двух проблем, которые сделали его ненадежным: inlining и тривиальность конструктор перемещения, он оказался рабочим методом.

Является ли сгенерированный конструктор перемещения неявным или явно дефолтным, не играет никакой роли, является ли значение по умолчанию тривиальным или нет: тривиальное движение ( и copy) конструктор просто выполнит байт-мудрую копию объекта.