Я получаю предупреждение под Clang при тестировании библиотеки под C ++ 11. Я никогда раньше не сталкивался с предупреждением, и поиск не дает слишком много возможностей для чтения и исследования.

Предупреждение показано ниже и, похоже, связано с множественным наследованием и общим базовым классом. Но я не понимаю, какие именно детали вызывают предупреждение или что мне делать, чтобы его устранить.

Мой первый вопрос: Нужно ли решать эту проблему? Или это вопрос только эффективности?

Мой второй вопрос (при необходимости): Как устранить предупреждение? Или какие есть варианты его устранения?


Вот дополнительная информация:

  • Компилятор: Apple LLVM версии 6.0 (clang-600.0.57) (на основе LLVM 3.5svn)
  • g ++ -DDEBUG -g2 -O2 -std = c ++ 11 -fPIC -march = native -pipe -c test.cpp

Также рассматривается следующее в Stack Overflow, но мне не ясно, где они пересекаются:

Библиотека Crypto ++ также активно использует Любопытно повторяющийся шаблон шаблона для полиморфизма времени компиляции.


Файл заголовка доступен в Интернете, и вот фактическое предупреждение:

g++ -DDEBUG -g2 -O2 -std=c++11  -Wno-deprecated-declarations -fPIC -march=native -pipe -c rsa.cpp
In file included from rsa.cpp:4:
In file included from ./rsa.h:12:
./pubkey.h:635:26: warning: defaulted move assignment operator of 'InvertibleRSAFunction' will move assign virtual base class 'CryptoMaterial' multiple times [-Wmultiple-move-vbase]
class CRYPTOPP_NO_VTABLE TF_ObjectImpl : public TF_ObjectImplBase<BASE, SCHEME_OPTIONS, KEY_CLASS>
                         ^
./rsa.h:57:44: note: 'CryptoMaterial' is a virtual base class of base class 'CryptoPP::RSAFunction' declared here
class CRYPTOPP_DLL InvertibleRSAFunction : public RSAFunction, public TrapdoorFunctionInverse, public PKCS8PrivateKey
                                           ^~~~~~~~~~~~~~~~~~
./rsa.h:57:96: note: 'CryptoMaterial' is a virtual base class of base class 'CryptoPP::PKCS8PrivateKey' declared here
class CRYPTOPP_DLL InvertibleRSAFunction : public RSAFunction, public TrapdoorFunctionInverse, public PKCS8PrivateKey
                                                                                               ^
1 warning generated.

Приношу свои извинения за то, что не уменьшил его. Я не уверен, как уменьшить его и уловить суть предупреждения / жалобы.

3
jww 1 Янв 2016 в 11:20

2 ответа

Лучший ответ

Предупреждение кажется мне самоочевидным, оно говорит вам, что присвоение перемещения производному типу приведет к двойному назначению перемещения базы.

Уменьшить его тривиально, просто создайте иерархию наследования, используя виртуальную базу и два пути к ней:

#include <stdio.h>

struct V {
    V& operator=(V&&) { puts("moved"); return *this; }
};

struct A : virtual V { };

struct B : virtual V { };

struct C : A, B { };

int main() {
    C c;
    c = C{};
}

Это напечатает "moved" дважды, потому что неявные операторы присваивания перемещения для каждого из A, B и C будут выполнять пословное присваивание, что означает, что оба {{X4} } и B::operator=(B&&) назначат базовый класс. Как говорит Алан, это действующая реализация стандарта. (Стандарт указывает, что при построении только наиболее производный тип будет создавать виртуальную основу, но у него нет таких же требований для присвоения).

Это не относится к перемещению присваивания, изменение базового класса для поддержки только копирования, но не перемещения присваивания напечатает "copied" дважды:

struct V {
    V& operator=(const V&) { puts("copied"); return *this; }
};

Это происходит по той же самой причине: как A::operator=(A&&), так и B::operator=(B&&) назначают базовый класс. Компилятор не предупреждает об этом случае, потому что повторное присвоение копий (вероятно) просто неоптимально, а не неправильно. При перемещении-назначении он может потерять данные.

Если в вашей виртуальной базе фактически нет данных, которые необходимо скопировать или переместить, или есть только элементы данных, которые можно легко копировать, то включение в нее только поддержки копирования без перемещения приведет к подавлению предупреждения:

struct V {
    V& operator=(const V&) = default;
};

Этот оператор присваивания копии по-прежнему будет вызываться дважды, но, поскольку он ничего не делает, проблем нет. Ничего не делать дважды - все равно ничего.

(GCC здесь кажется немного умнее, чем Clang, он не предупреждает о том, что оператор присваивания перемещения виртуальной базы вызывается дважды, если это тривиально, потому что тривиальный ход эквивалентен копии и поэтому с меньшей вероятностью будет проблемой).

Если в виртуальной базе есть данные, которые необходимо скопировать при назначении, то сделать копию, а не перемещение, все равно может быть хорошим выбором, но это зависит от типа и того, что делает. Возможно, вам потребуется явно определить назначение копирования и перемещения на каждом уровне иерархии. Виртуальные базы сложны, и их сложно использовать правильно, особенно при копировании или перемещении. Обработка типов с виртуальными базами как типов значений, которые можно легко копировать и перемещать, может быть ошибкой проектирования.

Иерархия iostreams использует виртуальные базы, но делается аккуратно и правильно. Типы iostream нельзя копировать, их можно только перемещать, а производные типы явно определяют назначение перемещения, чтобы гарантировать, что базовый класс basic_ios<> обновляется только один раз. В частности, basic_iostream::operator=(basic_iostream&&) работает только с базой basic_istream, а не с базой basic_ostream. Эквивалент для приведенного выше примера:

struct C : A, B {
     C& operator=(C&& c) {
         static_cast<A&>(*this) = static_cast<A&&>(c);
         return *this;
     }
};

Iostreams вообще нельзя было копировать до C ++ 11, когда ссылки rvalue и семантика перемещения сделали возможным использование полезной семантики. Если ваш класс всегда был копируемым в C ++ 03, это, возможно, уже было сомнительным дизайном, который должен был быть не копируемым, или иметь тщательно написанные операции копирования, а не неявно определенные.

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

6
Jonathan Wakely 7 Янв 2016 в 12:56

Стандарт позволяет реализациям выбирать простой, но иногда неверный способ обработки пословного присваивания при наличии виртуальных баз.

http://en.cppreference.com/w/cpp/language/move_assignment :

Как и в случае с копированием, не определено, назначаются ли подобъекты виртуального базового класса, которые доступны по более чем одному пути в решетке наследования, более одного раза неявно определенным оператором присваивания перемещения.

Это особенно неприятно при назначении перемещения, так как это может означать назначение от уже перемещенного члена.

4
Alan Stokes 1 Янв 2016 в 08:46