Почему этот тип неполон при использовании идиомы PIMPL?

Я использую идиому PIMPL, и, в частности, я использую шаблон из этого поста. Учитывая набор классов ниже и компиляцию с VS2015 Update 3, я получаю ошибки компиляции:

Ошибка C2027 использование неопределенного типа 'C::C_impl' (компиляция исходного файла src\A.cpp)

Ошибка C2338 не может удалить неполный тип (компиляция исходного файла src\A.cpp)

Предупреждение C4150 удаление указателя на неполный тип 'C::C_impl'; не вызывается деструктор (компиляция исходного файла src\A.cpp)

Я могу решить это, раскомментировав C::~C()что заставляет меня верить, что что-то мешает ~C() быть автоматически сгенерированным, но я не понимаю, что. Согласно этой ссылке деструктор для типа T неявно определяется как удаленный, если выполняется любое из следующего:

  1. T имеет нестатический элемент данных, который не может быть уничтожен (удален или недоступен деструктор)
  2. T имеет прямой или виртуальный базовый класс, который не может быть уничтожен (имеет удаленные или недоступные деструкторы)
  3. T является объединением и имеет вариантный член с нетривиальным деструктором.
  4. Неявно объявленный деструктор является виртуальным (поскольку базовый класс имеет виртуальный деструктор), и поиск функции освобождения (оператор delete() приводит к вызову неоднозначной, удаленной или недоступной функции).

Пункты № 2, 3 и 4, очевидно, не относятся к Cи я не верю, что № 1 применяется, потому что pimpl<> (Cединственный член) явно определяет деструктор.

Может кто-нибудь объяснить, пожалуйста, что происходит?

ах

#pragma once
#include <Pimpl.h>

class A
{
private:
    struct A_impl;
    pimpl<A_impl> m_pimpl;
};

Bh

#pragma once
#include "C.h"

class B
{
private:
    C m_C;
};

Ch

#pragma once
#include <Pimpl.h>

class C
{
public:
    // Needed for the PIMPL pattern
    //~C();

private:
    struct C_impl;
    pimpl<C_impl> m_pimpl;
};

a.cpp

#include <memory>
#include "A.h"
#include "B.h"
#include <PimplImpl.h>

struct A::A_impl
{
    std::unique_ptr<B> m_pB;
};

// Ensure all the code for the template is compiled
template class pimpl<A::A_impl>;

C.cpp

#include <C.h>
#include <PimplImpl.h>

struct C::C_impl { };

// Needed for the PIMPL pattern
//C::~C() = default;

// Ensure all the code for the template is compiled
template class pimpl<C::C_impl>;

Для полноты реализации PIMPL из поста, упомянутого выше:

pimpl.h

#pragma once
#include <memory>

template<typename T>
class pimpl
{
private:
    std::unique_ptr<T> m;
public:
    pimpl();
    template<typename ...Args> pimpl(Args&& ...);
    ~pimpl();
    T* operator->();
    T& operator*();
};

PimplImpl.h

#pragma once
#include <utility>

template<typename T>
pimpl<T>::pimpl() : m{ new T{} } {}

template<typename T>
template<typename ...Args>
pimpl<T>::pimpl(Args&& ...args)
    : m{ new T{ std::forward<Args>(args)... } }
{
}

template<typename T>
pimpl<T>::~pimpl() {}

template<typename T>
T* pimpl<T>::operator->() { return m.get(); }

template<typename T>
T& pimpl<T>::operator*() { return *m.get(); }

Несколько замечаний по поводу кода выше:

  • Я пытаюсь разоблачить A а также C потребителям моей библиотеки и хранить B внутренний.
  • Здесь нет B.cpp, это будет empy.

2 ответа

Решение

Вы должны определить деструктор C вручную после определения C::C_impl, потому что по умолчанию компилятор пытается сгенерировать деструктор C в точке использования, но это может быть точка, где определение C::C_impl не может быть найдено (например, это может быть B.cpp).

Я считаю, что проблема в том, что std::unique_ptr требует знания функции уничтожения (т. е. деструктора) во время создания экземпляра.

С деструктором по умолчанию ~C() компилятор генерирует его в точке использования, что означает, что он пытается удалить pimpl<C_impl> объект с доступной ему информацией в A.cpp, Конечно, так как C_impl объявляется только в тот момент, компилятор не знает, как уничтожить C_impl объект, и это ошибка, которую вы получаете.

раскомментировав ~C(); говорит компилятору не беспокоиться о том, как уничтожить C_impl, это будет определено где-то еще. В вашем случае это определено в C.cpp где определение C_impl известен.

В ответ на ваши изменения, деструктор для pimpl<C_impl> имеет std::unique_ptr<C_impl> который имеет нестатический член данных с недоступным деструктором (C_impl является неполным типом в момент использования в рамках A.cpp).

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