Неполный тип идиомы C ++ Pimpl с использованием std :: unique_ptr

Прошу прощения за большой объем кода, необходимый для демонстрации проблемы. У меня проблема с использованием идиомы pimpl с std :: unique_ptr. В частности, проблема возникает, когда один класс (который имеет реализацию с расширением) используется в качестве данных-члена в другом составном классе с реализацией с расширением.

Большинство ответов, которые мне удалось найти, связаны с отсутствием явное объявление деструктора, но, как вы можете видеть здесь, я объявил и определил деструкторы.

Что не так с этим кодом, и можно ли изменить его для компиляции без изменения дизайна?

Примечание: похоже, что ошибка возникает в определении SomeComposite :: getValue (), и компилятор не может увидеть ошибку до времени компиляции. Ошибка обнаружена в memory.h, и появляется сообщение: Недопустимое применение 'sizeof' к неполному типу 'pimplproblem :: SomeInt :: impl'.

SomeInt.h

#pragma once
#include <iostream>
#include <memory>

namespace pimplproblem
{
    class SomeInt
    {

    public:
        explicit SomeInt( int value );
        SomeInt( const SomeInt& other ); // copy
        SomeInt( SomeInt&& other ) = default; // move
        virtual ~SomeInt();
        SomeInt& operator=( const SomeInt& other ); // assign
        SomeInt& operator=( SomeInt&& other ) = default; // move assign
        int getValue() const;

    private:
        class impl;
        std::unique_ptr<impl> myImpl;
    };
}

SomeInt.cpp

#include "SomeInt.h"

namespace pimplproblem
{
    class SomeInt::impl
    {
    public:
        impl( int value )
        :myValue( value )
        {}

        int getValue() const
        {
            return myValue;
        }
    private:
        int myValue;
    };

    SomeInt::SomeInt( int value )
    :myImpl( new impl( value ) )
    {}

    SomeInt::SomeInt( const SomeInt& other )
    :myImpl( new impl( other.getValue() ) )
    {}

    SomeInt::~SomeInt()
    {}

    SomeInt& SomeInt::operator=( const SomeInt& other )
    {
        myImpl = std::unique_ptr<impl>( new impl( other.getValue() ) );
        return *this;
    }

    int SomeInt::getValue() const
    {
        return myImpl->getValue();
    }
}

SomeComposite.h

#pragma once
#include <iostream>
#include <memory>
#include "SomeInt.h"

namespace pimplproblem
{
    class SomeComposite
    {   

    public:
        explicit SomeComposite( const SomeInt& value );
        SomeComposite( const SomeComposite& other ); // copy
        SomeComposite( SomeComposite&& other ) = default; // move
        virtual ~SomeComposite();
        SomeComposite& operator=( const SomeComposite& other ); // assign
        SomeComposite& operator=( SomeComposite&& other ) = default; // move assign
        SomeInt getValue() const;

    private:
        class impl;
        std::unique_ptr<impl> myImpl;
    };
}

SomeComposite.cpp

#include "SomeComposite.h"

namespace pimplproblem
{
    class SomeComposite::impl
    {
    public:
        impl( const SomeInt& value )
        :myValue( value )
        {}

        SomeInt getValue() const
        {
            return myValue;
        }
    private:
        SomeInt myValue;
    };

    SomeComposite::SomeComposite( const SomeInt& value )
    :myImpl( new impl( value ) )
    {}

    SomeComposite::SomeComposite( const SomeComposite& other )
    :myImpl( new impl( other.getValue() ) )
    {}

    SomeComposite::~SomeComposite()
    {}

    SomeComposite& SomeComposite::operator=( const SomeComposite& other )
    {
        myImpl = std::unique_ptr<impl>( new impl( other.getValue() ) );
        return *this;
    }

    SomeInt SomeComposite::getValue() const
    {
        return myImpl->getValue();
    }
} 

person Matthew James Briggs    schedule 28.02.2015    source источник
comment
см. также stackoverflow.com/q/8595471/103167   -  person Ben Voigt    schedule 28.02.2015
comment
В случаях, когда эта загадочная ошибка возникает без использования конструкторов = default, я решил включить явный деструктор в мой класс.   -  person Matt Eding    schedule 10.07.2020


Ответы (1)


Вы не можете использовать конструкторы по умолчанию и операторы присваивания (такие как SomeInt( SomeInt&& other ) = default;), объявленные в файле заголовка с классами Pimpl, потому что реализации по умолчанию являются встроенными, а в момент объявления объявление SomeInt SomeInt::impl является неполным, поэтому unique_ptr жалуется. Вы должны сами объявить и определить вне очереди (то есть в файле реализации) все специальные функции-члены.

То есть измените объявления SomeInt и SomeComposite следующим образом:

// SomeInt.h
SomeInt( SomeInt&& other ); // move
SomeInt& operator=( SomeInt&& other ); // move assign

// SomeInt.cpp
// after definition of SomeInt::impl
SomeInt::SomeInt( SomeInt&& other ) = default;
SomeInt& operator=( SomeInt&& other ) = default;

Другой вариант - создать собственный указатель Pimpl, как предлагается в этом ответе.

person Anton Savin    schedule 28.02.2015
comment
Этот ответ stackoverflow.com/a/11212403/2779792 сбил меня с пути. Кажется, что ответ здесь совершенно неверен, поскольку он показывает завершенное объявление класса impl в файле заголовка (?), Что противоречит цели pimpl. Тогда ответ будет использовать ходы по умолчанию. Возможно, я не понял вопросов / ответов, потому что моя ситуация явно другая. - person Matthew James Briggs; 01.03.2015
comment
@MatthewJamesBriggs, самый высоко оцененный комментарий к этому вопросу, предлагает прочитать GOTW 100, что гораздо полезнее, чем ответ. Я также обновил свой ответ более конкретными инструкциями, что делать. - person Anton Savin; 01.03.2015
comment
@MatthewJamesBriggs: Этот ответ не является неправильным, но он работает по-другому и вообще не предоставляет брандмауэр компиляции. Обратите внимание, что в этом ответе тело impl находится в файле заголовка, а не в файле реализации, как ваш. Он также решает проблемы, возникающие при перемещении определения impl. - person Ben Voigt; 01.03.2015