防止 C++ 中的标头爆炸(或 C++0x)

发布于 2024-10-28 00:51:27 字数 2798 浏览 1 评论 0 原文

假设具有如下所示的通用代码:

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.cppx .cpp 分开,编译 main.cpp 时我不需要包含 y.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.hppx.cpp

< strong>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,因为它是在其他地方编译的”。

真的,我应该首先添加一个构造函数,如果没有它,我的程序就会出现错误。

Lets say with have generic code like the following:

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

Now, we want to be able to use a Y in a class (say X) we create. However, we don't want users of X to have to include the Y headers.

So we define a class X, something like this:

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

Note that, because y_ is a pointer, we don't need to include its implementation.

The implementation is in x.cpp, which is separately compiled:

x.cpp:

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

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

So now our clients can just include "x.hpp" to use X, without including and having to process all of "y.hpp" headers:

main.cpp:

#include "x.hpp"

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

And now we can compile main.cpp and x.cpp separately, and when compiling main.cpp I don't need to include y.hpp.

However with this code I've had to use a raw pointer, and furthermore, I've had to use a delete.

So here are my questions:

(1) Is there a way I could make Y a direct member (not a pointer to Y) of X, without needing to include the Y headers? (I strongly suspect the answer to this question is no)

(2) Is there a way I could use a smart pointer class to handle the heap allocated Y? unique_ptr seems like the obvious choice, but when I change the line in x.hpp

from:

Y<int>* y_; 

to:

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

and include , and compile with c++0x mode, I get the error:

/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"

so is there anyway to do this by using a standard smart pointer instead of a raw pointer and also a raw delete in a custom destructor?

Solution:

Howard Hinnant has got it right, all we need to do is change x.hpp and x.cpp in the following fashion:

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; }

And we're good to use unique_ptr. Thanks Howard!

Rationale behind solution:

People can correct me if I'm wrong, but the issue with this code was that the implicit default constructor was trying to default initialize Y, and because it doesn't know anything about Y, it can't do that. By explicitly saying we will define a constructor elsewhere, the compiler thinks "well, I don't have to worry about constructing Y, because it's compiled elsewhere".

Really, I should have added a constructor in the first place, my program is buggy without it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

为你拒绝所有暧昧 2024-11-04 00:51:27

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

shared_ptrunique_ptr 都可以防止您意外地对不完整的类型调用删除。这使得它们优于不提供此类保护的原始指针。 unique_ptr 需要概述 X() 的原因归结为它具有静态删除器而不是动态删除器。

编辑:更深入的说明

由于 unique_ptrshared_ptr 的静态删除器与动态删除器的差异,这两个智能指针要求 element_type 在不同的地方。

unique_ptr 要求 A 为完整:

  • ~unique_ptr();

但不适用于:

shared_ptr 要求 A 为完整:

但不适用于:

  • shared_ptr();
  • ~shared_ptr();

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

底线:任何调用智能指针成员(其中 element_type 要求完整)的 X 成员都必须概述到其中 element_type 的源。 > 已完成。

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

You can use either unique_ptr or shared_ptr to handle the incomplete type. If you use shared_ptr, you must outline ~X() as you have done. If you use unique_ptr you must outline both ~X() and X() (or whatever constructor you're using to construct X). It is the implicitly generated default ctor of X that is demanding a complete type Y<int>.

Both shared_ptr and unique_ptr are protecting you from accidentally calling delete on an incomplete type. That makes them superior to a raw pointer which offers no such protection. The reason unique_ptr requires the outlining of X() boils down to the fact that it has a static deleter instead of dynamic deleter.

Edit: Deeper clarification

Because of the static deleter vs dynamic deleter difference of unique_ptr and shared_ptr, the two smart pointers require the element_type to be complete in different places.

unique_ptr<A> requires A to be complete for:

  • ~unique_ptr<A>();

But not for:

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

shared_ptr<A> requires A to be complete for:

  • shared_ptr<A>(A*);

But not for:

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

And finally, the implicitly generated X() ctor will call both the smart pointer default ctor and the smart pointer dtor (in case X() throws an exception - even if we know it will not).

Bottom line: Any member of X that calls a smart pointer member where the element_type is required to be complete must be outlined to a source where the element_type is complete.

And the cool thing about unique_ptr and shared_ptr is that if you guess wrong on what needs to be outlined, or if you don't realize a special member is being implicitly generated that requires a complete element_type, these smart pointers will tell you with a (sometimes poorly worded) compile time error.

寄离 2024-11-04 00:51:27

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

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

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

编辑:已检查unique_ptr文档。似乎您可以使用它来代替:只需确保在构造 unique_ptr<> 的位置定义 ~X() 即可。

1) You are right, the answer is "no": compiler should know the size of member-object, and it cannot know it without having definition of Y type.

2) boost::shared_ptr (or tr1::shared_ptr) doesn't require complete type of object. So if you can afford overhead implied by it, it would help:

The class template is parameterized on T, the type of the object pointed to. shared_ptr and most of its member functions place no requirements on T; it is allowed to be an incomplete type, or void.

Edit: have checked unique_ptr docs. Seems you can use it instead: just be sure that ~X() is defined where unique_ptr<> is constructed.

美人迟暮 2024-11-04 00:51:27

如果您不喜欢使用 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 对象的显式额外指针。

If you don't like the extra pointer necessary to use the pimpl idiom, try this variant. First, define X as an abstract base class:

// x.hpp, guard #defines elided

class X
{
protected:
    X();

public:
    virtual ~X();

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

Note that Y doesn't feature here. Then, create an impl class which derives from X:

 #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 declared a factory function, create(). Implement this to return an XImpl:

// X.cpp

#include "XImpl.h"

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

Users of X can include X.hpp, which has no inclusion of y.hpp. You get something which looks a little like pimpl, but which doesn't have the explicit extra pointer to an impl object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文