代码之家  ›  专栏  ›  技术社区  ›  my_question

unique_ptr、皮条/转发声明和完整定义

  •  15
  • my_question  · 技术社区  · 11 年前

    我已经检查了问题 here here ,但仍然无法找出问题所在。

    这是呼叫代码:

    #include "lib.h"
    
    using namespace lib;
    
    int
    main(const int argc, const char *argv[]) 
    {
        return 0;
    }
    

    这是lib代码:

    #ifndef lib_h
    #define lib_h
    
    #include <string>
    #include <vector>
    #include <memory>
    
    namespace lib
    {
    
    class Foo_impl;
    
    class Foo
    {
        public:
            Foo();
            ~Foo();
    
        private:
            Foo(const Foo&);
            Foo& operator=(const Foo&);
    
            std::unique_ptr<Foo_impl> m_impl = nullptr;
    
            friend class Foo_impl;
    };
    
    } // namespace
    
    #endif
    

    clang++给了我这个错误:

    对不完整的类型“lib::Foo_impl”应用“sizeof”无效
    注意:在成员函数“std::default_delete::operator()”的实例化中请求

    你可以看到我已经特别声明了Foo析构函数。我还缺什么?

    4 回复  |  直到 9 年前
        1
  •  11
  •   Community Mohan Dere    9 年前

    实施 Foo_impl 必须在中要求的实例化之前完成 std::unique_ptr<Foo_impl> m_impl = nullptr .

    保留声明的类型(但不初始化)将修复错误( std::unique_ptr<Foo_impl> m_impl; ),然后需要稍后在代码中对其进行初始化。

    您看到的错误来自于用于测试此问题的技术的实现;不完整类型。大体上 sizeof 将导致仅前向声明的类型出现错误(即在代码/编译中使用时缺少定义)。

    这里可能的解决方案如下:;

    class Foo_impl;
    
    class Foo
    {
      // redacted
      public:
        Foo();
        ~Foo();
    
      private:
        Foo(const Foo&);
        Foo& operator=(const Foo&);
    
        std::unique_ptr<Foo_impl> m_impl;// = nullptr;
    };
    
    class Foo_impl {
      // ...
    };
    
    Foo::Foo() : m_impl(nullptr)
    {
    }
    

    为什么需要完整的类型?

    实例化通过 = nullptr 使用 copy initialisation 并要求声明构造函数和析构函数(for unique_ptr<Foo_impl> ). 析构函数需要 unique_ptr 默认情况下,调用 delete 在指向的指针上 食物_ mpl 因此它需要 食物_ mpl ,和的析构函数 食物_ mpl 未在不完整类型中声明(编译器不知道它是什么样子的)。看见 Howard's answer 在这方面也是如此。

    关键是 使命感 删去 不完全类型导致未定义行为 (§5.3.5/5),因此在实施 唯一_ptr .

    这种情况的另一种选择可能是使用 直接初始化 如下所示;

    std::unique_ptr<Foo_impl> m_impl { nullptr };
    

    关于 非静态数据成员初始化器 (NSDMI)以及这是否 需要存在成员定义的上下文 至少对于clang(以及可能的gcc)来说,这似乎是一个这样的背景。

        2
  •  9
  •   Community Mohan Dere    9 年前

    声明:

    std::unique_ptr<Foo_impl> m_impl = nullptr;
    

    调用 复制初始化 。这具有与以下相同的语义:

    std::unique_ptr<Foo_impl> m_impl = std::unique_ptr<Foo_impl>(nullptr);
    

    一、 e.它构建一个临时prvalue。必须销毁此临时prvalue。析构函数需要看到 Foo_impl 。即使省略了prvalue和move构造,编译器也必须表现得“好像”。

    您可以使用 直接初始化 ,以及 unique_ptr 此时不再需要析构函数:

    std::unique_ptr<Foo_impl> m_impl{nullptr};
    

    使现代化

    Casey 指出gcc-4.9当前实例化 ~unique_ptr() 即使是直接初始化形式。但是在我的测试中,clang没有。我不知道其他编译器可以做什么 相信 clang在这方面是符合的,至少考虑了最近的核心缺陷报告。

        3
  •  4
  •   David G    11 年前

    代替

    std::unique_ptr<Foo_impl> m_impl = nullptr;
    

    具有

    std::unique_ptr<Foo_impl> m_impl;
    

    以修复错误。

        4
  •  2
  •   Casey    11 年前

    N3936[temp.inst]/2状态:

    除非类模板或成员模板的成员已显式实例化或显式专用化,否则当在需要成员定义存在的上下文中引用专用化时,成员的专用化将隐式实例化;特别是,静态数据成员的初始化(以及任何相关的副作用)不会发生,除非静态数据成员本身以需要静态数据成员定义的方式使用。

    因此,这个问题实际上归结为带有非静态数据成员初始值设定项(NSDMI)的声明是否构成了“一个需要成员定义存在的上下文”。虽然很明显 声明 如果要确定NSDMI是否为初始化成员的适当类型,则立即需要类型构造函数的 定义 只有封闭类型的构造函数/析构函数才需要构造函数/析析构函数的,并且实现是不一致的。

    也就是说,NSDMI的语义存在几个问题,目前正在由核心语言小组进行审查:

    所以这里出现混乱并不奇怪。