наследование noncopyable не имеет никакого эффекта в классах dllexport - PullRequest
2 голосов
/ 20 сентября 2011

ОБНОВЛЕНИЕ нижеуказанная ошибка исправлена ​​в 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
}  

Вопросы

  1. В статье базы знаний упоминается исправление в следующем выпуске. Кто-нибудь может проверить, исправлено ли это уже в VS2010 (или, возможно, с предварительным просмотром Visual Studio 11)?

  2. Есть ли обходной путь, чтобы вызвать любую ошибку? Я попытался (ab) использовать тот факт, что запись return CantCopyMe() вызывает внутреннюю ошибку компилятора, но я не смог найти способ ее условного запуска только при компиляции функции, подобной MakeIt выше. Помещение static_assert в конструктор копирования noncopyable также не обрезает его, поскольку компилятор всегда компилирует его, даже если он не вызывается.

Ответы [ 2 ]

2 голосов
/ 20 сентября 2011

Чтобы ответить 1 (для VS2010), я только что попробовал его в VS2010 (с SP1), и он прекрасно компилируется, что означает, что он не был исправлен.К сожалению, у меня нет 2011 года для проверки

до 2. Я думаю, что один из способов сделать это будет:

  • больше не наследуется от некопируемого
  • объявитьскопируйте ctor и оператор присваивания как частный в CantCopyMe без предоставления реализации)
class CantCopyMe 
{
public:
   //omitted for brevity...
private:
    CantCopyMe( const CantCopyMe& );
    const CantCopyMe& operator=( const CantCopyMe& );
};

Сделав это, вы избежали описанных вами опасных ситуаций, и это должно работать и с VS2008.И вы решаете проблему в нужном месте, то есть в не копируемом объявлении класса.

1 голос
/ 07 октября 2011

Я только что столкнулся с этой же проблемой в несколько иной ситуации: у меня есть экспортированный класс 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.

...