使用 std::unique_ptr 的 C++ Pimpl Idiom 不完整类型

Posted

技术标签:

【中文标题】使用 std::unique_ptr 的 C++ Pimpl Idiom 不完整类型【英文标题】:C++ Pimpl Idiom Incomplete Type using std::unique_ptr 【发布时间】:2015-05-01 10:09:42 【问题描述】:

对于演示该问题所需的大量代码,我深表歉意。我在使用带有 std::unique_ptr 的 pimpl 习惯用法时遇到问题。具体来说,当一个类(具有 pimpl'ed 实现)用作另一个具有 pimpl'ed 实现的复合类中的成员数据时,似乎会出现问题。

我能找到的大多数答案都与缺少 explicit destructor declaration 相关,但正如您在此处看到的,我已经声明并定义了析构函数。

这段代码有什么问题,能不能在不改变设计的情况下修改编译?

注意:错误似乎发生在 SomeComposite::getValue() 的定义中,并且编译器直到编译时才能看到错误。在 memory.h 中遇到错误,消息是 Invalid application of 'sizeof' to an Complete type '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();
    
 

【问题讨论】:

另见***.com/q/8595471/103167 对于在不使用= default 构造函数的情况下发生这种神秘错误的情况,我的解决方案是在我的类中包含一个显式析构函数。 【参考方案1】:

您不能将在头文件中声明的默认构造函数和赋值运算符(例如 SomeInt( SomeInt&amp;&amp; other ) = default;)与 Pimpl 类一起使用,因为默认实现是内联的,并且在声明点 SomeInt 的声明 SomeInt::impl不完整,所以unique_ptr 抱怨。您必须自己声明和定义所有特殊成员函数(即在实现文件中)。

即修改SomeIntSomeComposite声明如下:

// 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 指针,如 this answer 中所建议的那样。

【讨论】:

这个答案***.com/a/11212403/2779792 让我误入歧途。似乎答案是完全错误的,因为它在头文件(?)中显示了 impl 类的完整声明,这违背了 pimpl 的目的。然后答案继续使用默认动作。也许我不明白 Q/A,因为我的情况显然不同。 @MatthewJamesBriggs 对该问题的最高评价建议阅读GOTW 100,这比答案有用得多。我还用更具体的说明更新了我的答案。 @MatthewJamesBriggs:这个答案没有错,但它做的事情不同,根本不提供编译防火墙。请注意,在那个答案中,impl 的主体在头文件中,而不是在你的实现文件中。它还解决了移动 impl 定义时出现的问题。

以上是关于使用 std::unique_ptr 的 C++ Pimpl Idiom 不完整类型的主要内容,如果未能解决你的问题,请参考以下文章

使用 std::unique_ptr 的 C++ Pimpl Idiom 不完整类型

C++ Qt std::unique_ptr Qt 版本在哪里?

深入了解C++ (15) | 源码分析auto_ptr & unique_ptr 设计

错误:使用已删除的函数‘std::unique_ptr<...> [关闭]

使用已使用 new X() 创建的变量将 std::unique_ptr 传递给函数

通过 std::unique_ptr 的 LazyArray 模板,这是双重检查习语的正确实现吗?