Наследование noncopyable не имеет никакого эффекта в классах dllexport
ОБНОВЛЕНИЕ нижеуказанная ошибка исправлена в VS2012, и noncopyable
работает как положено
Это и вопрос, и способ предоставить информацию / предупредить других, чтобы они не попали в ту же ловушку, что и я: кажется, что использование noncopyable
Базовый класс (как в Boost) не влияет на экспортируемые классы при использовании компилятора MS. Это известная ошибка для MS, но я сомневаюсь, что многие программисты знают об этом. Как можно себе представить, это может привести к крайне неприятным ошибкам, поскольку позволяет писать код, который даже не должен компилироваться. Пример (код для некопируемого класса здесь:)
типичный заголовочный файл в проекте dll, скомпилируйте с /D EXPORT_IT
:
#ifdef EXPORT_IT
#define mydll __declspec( dllexport )
#else
#define mydll __declspec( dllimport )
#endif
class mydll CantCopyMe : private noncopyable
{
public:
CantCopyMe();
~CantCopyMe();
};
mydll CantCopyMe MakeIt();
исходный файл:
#include <iostream>
CantCopyMe::CantCopyMe()
{
std::cout << "constructor" << std::endl;
}
CantCopyMe::~CantCopyMe()
{
std::cout << "destructor" << std::endl;
}
CantCopyMe MakeIt()
{
CantCopyMe x;
return x; //oops... this sould not compile nor link but it does
}
приложение:
int main()
{
CantCopyMe x( MakeIt() );
}
выход:
constructor
destructor
destructor
1 конструктор, 2 деструктора. Представьте себе проблемы, когда класс эффективно содержит ресурсы.
отредактируйте варианты использования, которые компилируются, но не должны:
CantCopyMe MakeIt()
{
CantCopyMe x;
return x;
}
void DoIt( CantCopyMe x )
{
x.Foo();
}
void SomeFun()
{
CantCopyMe x;
DoIt( x );
}
другие случаи: CantCopyMe MakeIt() { return CantCopyMe(); // фатальная ошибка C1001 }
CantCopyMe GenerateIt()
{
CantCopyMe x;
return x;
}
CantCopyMe MakeIt()
{
return GenerateIt(); //fatal error C1001
}
CantCopyMe MakeIt()
{
CantCopyMe x;
return CantCopyMe( x ); //fatal error C1001 + cl crashes
}
void DoSomething()
{
CantCopyMe x;
CantCopyMe y = x; //fatal error C1001 + cl crashes
}
Вопросы:
В статье базы знаний упоминается исправление в следующем выпуске. Кто-нибудь может проверить, исправлено ли это уже в VS2010 (или, возможно, с предварительным просмотром Visual Studio 11)?
Есть ли обходной путь, чтобы вызвать любую ошибку? Я пытался (ab), используя тот факт, что написание
return CantCopyMe()
вызывает внутреннюю ошибку компилятора, но я не смог найти способ ее условного запуска только при компиляции такой функции, какMakeIt
выше. Помещение static_assert в конструктор копирования noncopyable также не обрезает его, так как компилятор всегда компилирует его, даже если он не вызывается.
2 ответа
Чтобы ответить 1 (для VS2010), я только что попробовал его в VS2010 (с SP1), и он прекрасно компилируется, что означает, что он не был исправлен. К сожалению у меня нет 2011 года для тестирования
Для 2. Я думаю, что один из способов сделать это будет:
- больше не происходит от некопируемого
- объявить копию ctor и оператор присваивания как частный в CantCopyMe без предоставления реализации)
class CantCopyMe { public: //omitted for brevity... private: CantCopyMe( const CantCopyMe& ); const CantCopyMe& operator=( const CantCopyMe& ); };
Сделав это, вы избежали опасных ситуаций, которые вы описали, и это должно работать и с VS2008. И вы решаете проблему в нужном месте, то есть в не копируемом объявлении класса.
Я столкнулся с этой же проблемой в несколько иной ситуации: у меня есть экспортированный класс DLL, которому был предоставлен элемент, не подлежащий копированию. Экспортируемый класс DLL не имеет явного конструктора копирования и имеет метод Copy, который возвращает свою копию в куче. Когда был добавлен элемент, не подлежащий копированию, не было ни ошибки компилятора, а неприятной ошибки времени выполнения. Я отследил его до __declspec(dllexport) и обнаружил, что если я его удалил, то получил ожидаемую и правильную ошибку компилятора, препятствующую копированию. Рассмотрим этот минимальный пример:
#define API __declspec(dllexport)
class Inner
{
public:
Inner() {}
private:
Inner(const Inner&) {}
Inner& operator=(const Inner&) { return *this; }
};
class API Outer
{
private:
Inner i;
public:
virtual Outer* Copy()
{
return new Outer(*this);
}
};
Когда я компилирую это с последней версией VS2010, я получаю: error C4716: 'Outer::Copy' : must return a value
, Если я изменю Copy () на это:
virtual Outer* Copy()
{
Outer* copy = new Outer(*this);
return copy;
}
Теперь я получаю только странное предупреждение: warning C4700: uninitialized local variable 'copy' used
и неприятный сбой во время выполнения. Наконец, попробуйте:
virtual Outer* Copy()
{
Outer tmp(*this);
return nullptr;
}
Компилятор надежно вылетит! Это на VS2010 SP1, версия C++ компилятора 16.00.40219.01 для 80x86.