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

当你不想#include时,可以选择转发声明

  •  10
  • sharkin  · 技术社区  · 16 年前

    我通常几乎不再思考,使用前向声明,这样我就不必包含标头。关于这个例子:

    //-----------------------
    // foo.h
    //-----------------------
    class foo
    {
       foo();
       ~foo();
    };
    
    
    //-----------------------
    // bar.h
    //-----------------------
    
    class foo; // forward declaration
    
    class bar
    {
       bar();
       ~bar();
    
       foo* foo_pointer;
    };
    

    一些开发人员喜欢使用这种方法来避免包含圆的问题。我宁愿用它来最小化广泛包含层次结构中的开销,这是物理设计的重要组成部分(特别是对于大型项目)。

    然而,在某些情况下,我真的很喜欢将成员声明为普通对象,而不是从自动构造/销毁机制中受益的指针。这导致了不能再使用正向声明的问题,因为在这种情况下编译器需要类定义,例如:

    //-----------------------
    // foo.h
    //-----------------------
    class foo
    {
       foo();
       ~foo();
    };
    
    
    //-----------------------
    // bar.h
    //-----------------------
    
    class foo;       // Not enough given the way we declare "foo_object"..
    #include "foo.h" // ..instead this is required
    
    class bar
    {
       bar();
       ~bar();
    
       foo foo_object;
    };
    

    因此,如果有人知道一种可以在这里使用的替代语言构造,我会很高兴,这样我就可以如示例所示声明“foo_object”,但不包括它的头。

    当做

    /罗伯特

    10 回复  |  直到 16 年前
        1
  •  7
  •   community wiki 5 revs Eclipse    16 年前

    你不能。编译器在声明类时需要知道对象的大小。

    引用是一种替代方案,尽管它们必须在构造时实例化,因此并不总是可行的。

    另一种选择是智能指针,但我认为从技术上讲,它仍然是一个指针。

    不过,最好知道为什么你不想使用指针来建议其他构造。..

        2
  •  12
  •   Pieter    16 年前

    只需使用智能指针——在这种情况下,您甚至可以使用auto_ptr。

    //-----------------------
    // bar.h
    //-----------------------
    
    #include <memory>
    class foo;       // Not enough given the way we declare "foo_object"..
    
    class bar
    {
    public:
       bar();
       ~bar();
    
       foo &foo_object() { return *foo_ptr; }
       const foo &foo_object() const { return *foo_ptr; }
    
    private:
       auto_ptr<foo> foo_ptr;
    };
    

    您可以获得自动内存管理的所有好处,而无需了解bar.h中的foo。请参阅 Wrapping Pointer Data Members 感谢赫伯·萨特的推荐。

    如果你真的希望默认构造自动发生,请尝试以下操作:

    #include <iostream>
    using namespace std;
    
    class Foo;
    
    template <typename T>
    class DefaultConstuctorPtr
    {
        T *ptr;
        void operator =(const DefaultConstuctorPtr &);
        DefaultConstuctorPtr(const DefaultConstuctorPtr &);
    
    public:
        DefaultConstuctorPtr() : ptr(new T()) {}
        ~DefaultConstuctorPtr() { delete ptr; }
    
        T *operator *() { return ptr; }
        const T *operator *() const { return ptr; }
    };
    
    class Bar
    {
        DefaultConstuctorPtr<Foo> foo_ptr;
    public:
        Bar() {} // The compiler should really need Foo() to be defined here?
    };
    
    class Foo
    {
    public:
        Foo () { cout << "Constructing foo"; }
    };
    
    int main()
    {
        Bar bar;
    }
    
        3
  •  7
  •   Tyler McHenry    16 年前

    你想要的东西在C++中是做不到的。为了为对象生成代码,编译器需要知道其类需要多少存储空间。为了知道这一点,它必须知道类的每个成员需要多少存储空间。

    如果你想创建一个包含foo类型成员的bar类型的类,编译器必须知道foo有多大。它知道这一点的唯一方法是它是否有foo的定义(通过#include)。否则,您唯一的选择就是使用foo的前向声明和指针或引用,而不是实际的foo对象。

        4
  •  2
  •   Johannes Schaub - litb    16 年前

    正如其他人所说,你不能这样做,因为他们也说:)然后你说你不想关心包含它们的类中的成员构造/销毁。您可以为此使用模板。

    template<typename Type>
    struct member {
        boost::shared_ptr<Type> ptr;
        member(): ptr(new Type) { }
    };
    
    struct foo;
    struct bar {
        bar();
        ~bar();
    
        // automatic management for m
        member<foo> m;
    };
    

    我认为代码是不言自明的。如果有任何问题,请打扰我。

        5
  •  1
  •   Stack Overflow is garbage    16 年前

    这是没有办法的。

    你最好的办法是限制包含的内容,但你必须在类声明中包含文件。您可以将类声明拆分为一个单独的标头,希望其中不包含其他内容。然后,是的,你必须有一个#include,但你仍然让你的include层次结构有点浅。毕竟,包含一个文件很便宜,只有当层次结构扩展到数百或数千个文件时,它才会开始受到伤害。.. ;)

        6
  •  1
  •   Community CDub    8 年前

    几乎你唯一能做的就是通过以下方式尽量减少影响 using the pImpl idiom 因此,当你包含foo.h时,你只包含foo的接口。

    你无法避免包含foo.h,但你可以让它尽可能便宜。你养成了使用向前声明而不是#includes的习惯,这让你在这条路上走得很好。

        7
  •  0
  •   C. K. Young    16 年前

    如果能够使用引用,则可以保留相同的使用语法。然而,你的引用必须在构造函数中立即初始化,所以你的ctor绝对必须被定义得不符合要求。(您还需要释放析构函数中的对象。)

    // bar.h
    class foo;
    
    class bar {
        foo& foo_;
    
    public:
        bar();
        ~bar();
    };
    
    // bar.cc
    bar::bar() : foo_(*new foo)
    {
        // ...
    }
    
    bar::~bar()
    {
        // ...
        delete &foo_;
    }
    

    您的里程数可能会有所不同。 :-)

        8
  •  0
  •   James Eichele Bernard Igiri    16 年前

    您可以使用一个自定义的“智能指针”类,该类可以自动创建和销毁实例。这将实现您所追求的自动构建和销毁。

    为了防止需要另一个#include,您可以包含以下内容 myAuto 在项目的前缀标题中添加类,或者您可以将其复制并粘贴到每个标题中(这不是一个好主意,但它会起作用)。

    template<class T>
    class myAuto
    {
        private:
            T * obj;
    
        public:
            myAuto() : obj(new T) {  }
            ~myAuto() { delete obj; }
            T& object() { return *obj; }
            T* operator ->() { return obj; }
    };

    以下是您将如何使用它:

    // foo.h:
    class foo
    {
        public:
            foo();
            ~foo();
            void some_foo_func();
    };
    //bar.h:
    class foo;
    class bar
    {
        public:
           bar();
           ~bar();
           myAuto<foo> foo_object;
    };
    
    //main.cc:
    #include "foo.h"
    #include "bar.h"
    
    int main()
    {
        bar a_bar;
    
        a_bar.foo_object->some_foo_func();
    
        return 0;
    }
        9
  •  0
  •   Rob    16 年前

    你也可以使用pImpl习语,例如:

    //-----------------------
    // foo.h
    //-----------------------
    class foo
    {
        foo();
        ~foo();
    };
    
    
    //-----------------------
    // bar.h
    //-----------------------
    
    class foo;
    
    class bar
    {
    private:
        struct impl;
        boost::shared_ptr<impl> impl_;
    public:
        bar();
    
        const foo& get_foo() const;
    };
    
    //-----------------------
    // bar.cpp
    //-----------------------
    #include "bar.h"
    #include "foo.h"
    
    struct bar::impl
    {
        foo foo_object;
        ...
    }
    
    bar::bar() :
    impl_(new impl)
    {
    }
    
    const foo& bar::get_foo() const
    {
        return impl_->foo_object;
    }
    

    您仍然可以获得正向声明的好处,并且可以隐藏您的私有实现。对bar实现的更改不一定需要编译所有包含bar.h的源文件。实现结构本身在.cpp文件中是自包含的,在这里你可以向你的heart内容声明对象。

    由于pImpl本身,您的性能受到了轻微的影响,但根据应用程序的不同,这可能不是什么大问题。

    我曾将pImpl习惯用法用于大型项目,这对编译时间有很大影响。遗憾的是,该语言无法处理真正的私有实现,但你已经拥有了它。

        10
  •  0
  •   MSalters    16 年前

    实际上只有三种方法可以关联两个对象。 你已经发现了两种方法:在Bar中嵌入Foo,或者把Foo放在堆上,然后在Bar中放一个Foo*。第一个要求在定义类Bar之前定义类Foo;第二个只需要你转发声明类Foo。

    第三种选择确实存在,我之所以提到它,是因为你在问题中明确排除了前两种选择。您可以(在.cpp中)创建一个静态std::map。在每个Bar构造函数中,你都会向这个映射添加一个Foo,并键入 this 。然后,每个酒吧成员都可以通过查找来找到相关的Foo 在地图上。酒吧:~酒吧会打电话 erase(this) 摧毁Foo。

    虽然这使sizeof(Bar)保持不变,但实际的内存使用量高于在Bar中包含Foo*。不过,如果二进制兼容性是一个紧迫的问题,您仍然可以这样做。

    推荐文章