Можно ли создать экземпляр неагрегированного класса с удаленными конструкторами и деструктором?

Рассмотрим следующий класс:

template <class T>
class object {
public:
    using type = T;

    object() = delete;
    object(const object&) = delete;
    object(object&&) = delete;
    object& operator=(const object&) = delete;
    object& operator=(object&&) = delete;
    ~object() = delete;

private:
    type value = 0;
};

Возможно ли создать экземпляр объекта (в стеке (возможно, нет) или в куче (возможно?)) Типа object<int> с помощью какой-либо странной уловки, или object<int> невозможно когда-либо существовать в каком-либо мыслимом (хорошо сформированном и с хорошо определенное поведение) программа C++17?

Приложение: Сначала мы рассмотрим неформальные программы NDR / поведение, отличное от неопределенного. Если и только если возможны только плохо сформированные программы отчета о недоставке / программы неопределенного поведения, вы можете проиллюстрировать ответ с помощью такой программы.

Могу я спросить, чем это полезно?

Passer By 11.04.2018 13:22

@PasserBy Это в основном теоретический вопрос

Vincent 11.04.2018 13:23
object<int> может быть создан, если вы напишете для него специализация, который это разрешит. Не может быть создан из этого универсального класса.
Jarod42 11.04.2018 13:55

Вы можете создать указатель void* и преобразовать его в object<T>*, верно? Это определенное поведение, не так ли? Хотя это не определенное значение.

freakish 11.04.2018 13:56

@freakish Нет, это не так.

Passer By 11.04.2018 13:58

@PasserBy Стандарт явно разрешает приведение из void* к любому типу указателя. Как это не определенное поведение? Или я ошибаюсь?

freakish 11.04.2018 14:00

@freakish - Там нет объекта. Во всяком случае, не для такого типа.

@PasserBy вы могли бы использовать это для коллекции статических функций ... большинство людей поместили бы их в пространство имен как бесплатные функции, но вот альтернатива.

UKMonkey 11.04.2018 14:32

Создайте буфер и используйте приведение. Таким образом, объект можно использовать без инициализации или разрушения. Ответ прилагается.

Robert Andrzejuk 21.04.2018 19:12
10
9
206
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я не утверждаю, что это ответ (я не языковой юрист), просто опубликуйте некоторые выводы из n4659:

11.6.20: An object whose initialization has completed is deemed to be constructed, even if no constructor of the object’s class is invoked for the initialization. [ Note: Such an object might have been value-initialized or initialized by aggregate initialization (11.6.1) or by an inherited constructor (15.6.3). —end note ]

Из трех вариантов, упомянутых в примечании, применяется только первый:

11.6.8: To value-initialize an object of type T means ... (8.1) — if T is a (possibly cv-qualified) class type (Clause 12) with either no default constructor (15.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;

Поскольку конструктор по умолчанию удален:

11.6.7: To default-initialize an object of type T means ... (7.1) — If T is a (possibly cv-qualified) class type (Clause 12), constructors are considered. The applicable constructors are enumerated (16.3.1.3), and the best one for the initializer () is chosen through overload resolution (16.3). The constructor thus selected is called, with an empty argument list, to initialize the object.

Поскольку нет конструктора, который можно было бы вызвать, я думаю, что экземпляр класса object не может быть построен.

Ответ принят как подходящий

Нет, не может. От [basic.life]

[...] The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and

  • if the object has non-vacuous initialization, its initialization is complete,

Таким образом, инициализация должна быть выполнена. Затем мы смотрим на все возможные инициализации.

  1. Если инициализатор - {...}, то это список инициализирован.

    • Класс не агрегатный, поэтому он не может быть агрегатно-инициализированный.
    • Класс не имеет конструктора по умолчанию, поэтому он не может быть инициализирован значением {}.
    • Класс может быть инициализирован напрямую или инициализирован копией с помощью {x}, который вызывает конструктор через разрешение перегрузки.
  2. Если инициализатор - (), то он инициализируется значением.

  3. Если инициализатор имеет тот же тип cv-unqualified, выражение инициализатора для инициализатора используется вместо инициализатора

    • То есть T x = T(T()); просто инициализирует x по умолчанию, без копий.
  4. Если инициализаторов нет, то он инициализируется по умолчанию.

  5. В противном случае конструкторы рассматриваются и вызываются с разрешением перегрузки.

  6. В данном случае инициализация значения означает инициализацию объекта по умолчанию.

  7. В этом случае инициализация по умолчанию вызывает конструктор по умолчанию.

Если вы внимательно посмотрите, в этом списке есть только один способ инициализировать объект - это вызвать конструктор. Поскольку мы удалили все конструкторы, мы не можем инициализировать объект.

Правила понятны, этот объект не может быть правильно инициализирован.

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

template< typename T >
class object_wrapper
{
    std::byte buffer[ sizeof( T ) ];

public:
    auto& inner() { return *reinterpret_cast< T* >( buffer ); }
};

Вот пример:

int main()
{
    //object< int > o; // impossible

    object_wrapper< object< int > > ow; // wrap the object

    object< int >& o( ow.inner() );     // possible

    o.some_foo();

}

Другие вопросы по теме