6

可以说具有如下通用代码:

y.hpp:

#ifndef Y_HPP
#define Y_HPP

// LOTS OF FILES INCLUDED

template <class T>
class Y 
{
public:
  T z;
  // LOTS OF STUFF HERE
};

#endif

现在,我们希望能够在我们创建的类(比如 X)中使用 Y。但是,我们不希望 X 的用户必须包含 Y 标头。

所以我们定义了一个类 X,如下所示:

x.hpp:

#ifndef X_HPP
#define X_HPP

template <class T>
class Y;

class X
{
public:
  ~X();
  void some_method(int blah);
private:
  Y<int>* y_;
};

#endif

注意,因为 y_ 是一个指针,我们不需要包含它的实现。

实现在 x.cpp 中,单独编译:

x.cpp:

#include "x.hpp"
#include "y.hpp"

X::~X() { delete y_; }
void X::someMethod(int blah) { y_->z = blah; }

所以现在我们的客户可以只包含“x.hpp”来使用X,而不需要包含并且必须处理所有的“y.hpp”头文件:

main.cpp:

#include "x.hpp"

int main() 
{
  X x;
  x.blah(42);
  return 0; 
}

现在我们可以单独编译main.cpp和编译了,编译时我不需要包含.x.cppmain.cppy.hpp

但是,对于这段代码,我不得不使用原始指针,此外,我不得不使用删除。

所以这是我的问题:

(1)有没有一种方法可以使 Y 成为 X 的直接成员(而不是指向 Y 的指针),而无需包含 Y 标头?(我强烈怀疑这个问题的答案是否定的)

(2)有没有办法可以使用智能指针类来处理堆分配的 Y?unique_ptr似乎是显而易见的选择,但是当我改变线路时x.hpp

从:

Y<int>* y_; 

到:

std::unique_ptr< Y<int> > y_;

并包含,并使用 c++0x 模式编译,我收到错误:

/usr/include/c++/4.4/bits/unique_ptr.h:64: error: invalid application of ‘sizeof’ to incomplete type ‘Y<int>’ 
/usr/include/c++/4.4/bits/unique_ptr.h:62: error: static assertion failed: "can't delete pointer to incomplete type"

那么有没有办法通过使用标准智能指针而不是原始指针以及自定义析构函数中的原始删除来做到这一点?

解决方案:

Howard Hinnant 做对了,我们需要做的就是x.hpp按照x.cpp以下方式进行更改:

x.hpp:

#ifndef X_HPP
#define X_HPP

#include <memory>

template <class T>
class Y;

class X
{
public:
  X(); // ADD CONSTRUCTOR FOR X();
  ~X();
  void some_method(int blah);
private:
  std::unique_ptr< Y<int> > y_;
};

#endif

x.cpp:

#include "x.hpp"
#include "y.hpp"

X::X() : y_(new Y<int>()) {} // ADD CONSTRUCTOR FOR X();
X::~X() {}
void X::someMethod(int blah) { y_->z = blah; }

我们很适合使用 unique_ptr。谢谢霍华德!

解决方案背后的理由:

如果我错了,人们可以纠正我,但这段代码的问题是隐式默认构造函数试图默认初始化 Y,因为它对 Y 一无所知,所以它不能这样做。通过明确地说我们将在别处定义一个构造函数,编译器认为“好吧,我不必担心构造 Y,因为它是在别处编译的”。

真的,我应该首先添加一个构造函数,没有它我的程序是错误的。

4

3 回答 3

12

您可以使用unique_ptrshared_ptr来处理不完整的类型。如果你使用,你必须像你所做的那样shared_ptr概述。~X()如果您使用unique_ptr,则必须同时概述~X()and X()(或您用于构造的任何构造函数X)。它是隐式生成的默认 ctor ,X它需要一个完整的类型Y<int>

两者shared_ptrunique_ptr保护您不会在不完整的类型上意外调用 delete。这使得它们优于不提供这种保护的原始指针。原因unique_ptr需要概述X()归结为它具有静态删除器而不是动态删除器的事实。

编辑:更深层次的澄清

由于 和 的静态删除器与动态删除器的区别unique_ptrshared_ptr这两个智能指针需要element_type在不同的地方完成。

unique_ptr<A>要求 A 完成:

  • ~unique_ptr<A>();

但不适用于:

  • unique_ptr<A>();
  • unique_ptr<A>(A*);

shared_ptr<A>要求 A 完成:

  • shared_ptr<A>(A*);

但不适用于:

  • shared_ptr<A>();
  • ~shared_ptr<A>();

最后,隐式生成的X()ctor 将调用智能指针默认 ctor智能指针 dtor(以防X()抛出异常——即使我们知道它不会)。

底线:X调用智能指针成员的任何成员都element_type必须将其概述到完整的源element_type

unique_ptr并且很酷的事情shared_ptr是,如果您对需要概述的内容猜错了,或者如果您没有意识到正在隐式生成需要完整的特殊成员element_type,这些智能指针会告诉您(有时措辞不佳) 编译时错误。

于 2011-03-28T14:45:46.200 回答
7

1)你是对的,答案是“不”:编译器应该知道成员对象的大小,如果没有定义 Y 类型,它就无法知道它。

2) boost::shared_ptr(or tr1::shared_ptr) 不需要完整的对象类型。因此,如果您能负担得起它隐含的开销,它将有所帮助:

类模板在 T 上参数化,即指向的对象的类型。shared_ptr 及其大部分成员函数对 T 没有要求;它可以是不完整的类型,或者是无效的。

编辑:检查过unique_ptr文档。似乎您可以改用它:只需确保在构造~X()位置上定义了unique_ptr<>它。

于 2011-03-28T14:17:38.347 回答
0

如果您不喜欢使用 pimpl 惯用语所需的额外指针,请尝试此变体。首先,将 X 定义为抽象基类:

// x.hpp, guard #defines elided

class X
{
protected:
    X();

public:
    virtual ~X();

public:
    static X * create();
    virtual void some_method( int blah ) = 0;
};

请注意,这里没有 Y。然后,创建一个从 X 派生的 impl 类:

 #include "Y.hpp"
    #include "X.hpp"

class XImpl 
: public X
{
    friend class X;

private:
    XImpl();

public:
    virtual ~XImpl();

public:
    virtual void some_method( int blah ) = 0;

private:
    boost::scoped_ptr< Y< int > > m_y;
};

X 声明了一个工厂函数 create()。实现它以返回一个 XImpl:

// X.cpp

#include "XImpl.h"

X * X::create()
{
    return new XImpl();
}

X 的用户可以包含 X.hpp,其中不包含 y.hpp。你得到的东西看起来有点像 pimpl,但它没有指向 impl 对象的显式额外指针。

于 2011-03-28T14:45:37.227 回答