не копируемые объекты и инициализация значения: g ++ vs msvc - PullRequest
4 голосов
/ 20 апреля 2010

Я наблюдаю различное поведение между g ++ и msvc вокруг значения, инициализирующего не копируемые объекты. Рассмотрим класс, который нельзя скопировать:

class noncopyable_base
{
public:
    noncopyable_base() {}

private:
    noncopyable_base(const noncopyable_base &);
    noncopyable_base &operator=(const noncopyable_base &);
};

class noncopyable : private noncopyable_base
{
public:
    noncopyable() : x_(0) {}
    noncopyable(int x) : x_(x) {}

private:
    int x_;
};

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

template <class T>
void doit()
{
    T t = T();
    ...
}

и пытаюсь использовать их вместе:

doit<noncopyable>();

Это прекрасно работает на msvc начиная с VC ++ 9.0, но не на всех версиях g ++, с которыми я тестировал (включая версию 4.5.0), потому что конструктор копирования является закрытым.

Два вопроса:

  1. Какое поведение соответствует стандартам?
  2. Любое предложение о том, как обойти это в gcc (и, чтобы быть понятным, изменение этого значения на T t; не является приемлемым решением, поскольку это нарушает типы POD).

P.S. Я вижу ту же проблему с boost :: noncopyable.

Ответы [ 4 ]

8 голосов
/ 20 апреля 2010

Поведение, которое вы видите в MSVC, является расширением, хотя оно документировано как таковое на следующей странице (выделено мое) http://msdn.microsoft.com/en-us/library/0yw5843c.aspx:

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

  • Создание временного объекта того же типа, что и инициализируемый объект.
  • Копирование временного объекта в объект.

Конструктор должен быть доступен, прежде чем компилятор сможет выполнить эти шаги. Хотя компилятор может исключить временные этапы создания и копирования в большинстве случаев, недоступный конструктор копирования приводит к сбою инициализации знака равенства ( в / Za, / Ze (Disable Language Extensions) ).

См. Ответ Бена Фойгта для обходного пути, который является упрощенной версией boost::value_initialized, на что указывает litb в комментарии к ответу Бена. В документах по boost::value_initalized подробно обсуждается проблема, обходной путь и некоторые подводные камни, связанные с различными проблемами компилятора.

5 голосов
/ 20 апреля 2010

Я не думаю, что шаблонное метапрограммирование необходимо. Попробуйте

template <class T>
void doit()
{
    struct initer { T t; initer() : t() {} } inited;
    T& t = inited.t;
    ...
}
3 голосов
/ 20 апреля 2010

Есть §12,8 / 14:

Программа некорректна, если неявно используется конструктор копирования или оператор присвоения копии для объекта и функция специального члена недоступна.

А потом есть §12,8 / 15:

Когда определенные критерии удовлетворены, реализация может опустить конструкцию копирования объекта класса, даже если конструктор копирования и / или деструктор для объекта имеют побочные эффекты.

Итак, вопрос в том, действительно ли, если реализация пропускает вызов конструктора копирования (что ему явно разрешено делать), действительно ли конструктор копирования используется ?

И ответ на этот вопрос - да, согласно §3.2 / 2:

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

0 голосов
/ 20 апреля 2010

Видели ли вы, что происходит при компиляции с использованием / Wall с MSVC? О вашем классе говорится следующее:

nocopy.cc(21) : warning C4625: 'noncopyable' : copy constructor could not be
generated because a base class copy constructor is inaccessible
nocopy.cc(21) : warning C4626: 'noncopyable' : assignment operator could not be
generated because a base class assignment operator is inaccessible

Средство GCC: создать конструктор копирования для noncopyable (и в идеале оператор присваивания!), который делает все возможное, чтобы скопировать информацию из noncopyable_base, а именно вызвать конструктор для noncopyable_base, который не имеет параметров (поскольку это единственный доступный noncopyable), а затем копировать любые данные из noncopyable_base. Однако, учитывая определение noncopyable_base, кажется, что данных для копирования нет, поэтому простое добавление noncopyable_base() в список инициализаторов новой функции noncopyable(const noncopyable &) должно работать.

Обратите внимание, что MSVC сказала о вашей программе. Также обратите внимание, что если вы используете T t() вместо T t = T(), другое предупреждение (C4930) генерируется MSVC, хотя GCC с радостью принимает его в любом случае без какого-либо предупреждения.

...