精华内容
下载资源
问答
  • 前置声明

    2021-01-12 19:05:04
    前置声明,也称前向声明(forward declaration)。在声明之后,定义之前,类Screen是个不完整类型(incomplete type),即已知Screen是一个类型,但是不知道包含哪些成员。 不完全类型只能以有限方式使用。不能定义该...

    前置声明,也称前向声明(forward declaration)。在声明之后,定义之前,类Screen是个不完整类型(incomplete type),即已知Screen是一个类型,但是不知道包含哪些成员。

    不完全类型只能以有限方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。

    展开全文
  • 详解C++ 前置声明

    2020-12-17 00:45:02
    前置声明是C/C++开发中比较常用的技巧,主要用在三种情形: 变量/常量,例如extern int var1;; 函数,例如void foo();,注意类的成员函数无法单独做前置声明; 类,例如class Foo;,也可以前置声明模板类:...
  • 前置声明是C/C++开发中比较常用的技巧,主要用在三种情形:变量/常量,例如extern int var1;;函数,例如void foo();,注意类的成员函数无法单独做前置声明;类,例如class Foo;,也可以前置声明模板类:template ...

    前置声明是C/C++开发中比较常用的技巧,主要用在三种情形:

    变量/常量,例如extern int var1;;

    函数,例如void foo();,注意类的成员函数无法单独做前置声明;

    类,例如class Foo;,也可以前置声明模板类:template classFoo;。如果类包含在名字空间中,需在名字空间内做前置声明:namespace tlanyan {class Foo;};,而不能这样:class tlanyan::Foo;。

    前置声明作用

    根据其用途,前置声明的主要作用为:

    避免重复定义变量;

    避免引入函数定义/声明文件,从而函数文件发生更改时不会重新编译依赖文件;

    解决循环依赖问题。

    前两种用途好理解,第三种稍微复杂点,但却是前置声明最重要的用途。其解决类A包含类B,同时类B包含类A的依赖问题。循环依赖一般是设计层面的问题,可通过接口、引入辅助类等手段化解。前置声明也能解决,只是架构上稍微别扭。

    不管A和B是否定义在同一个文件中,c++永远无法解决如下形式的循环依赖(后文解释原因):

    // file: A.hpp

    #include "B.hpp"

    class A {

    int id;

    B b;

    };

    // file: B.hpp

    #include "A.hpp"

    class B {

    ...

    A a;

    };

    前置声明解决该问题需要与指针配合,转换成另一种形式。要点如下:

    至少将某类的变量类型转换成指针,例如A中将B转成B*;

    类A中对B使用前置声明;

    类A的定义文件中移除对类B文件的包含(做了包含保护则可忽略)。

    使用前置声明后,以下是一种可行的解决形式(两个类均使用了前置声明):

    // file: A.hpp

    //3. 移除对B的包含(使用了#pragma once或者#ifndef B_HPP等保护措施则无必要)

    // 2. 前置声明类B

    class B;

    class A {

    int id;

    // 1. 成员变量转换成指针

    B* b;

    };

    // file: B.hpp

    // 3. 移除对A的包含(有包含保护则非必要)

    // 2. 前置声明类A

    class B {

    ...

    // 1. 成员变量转换成指针

    A* a;

    };

    深入前置声明

    如果你有其他编程语言的经验,会发现c++有点怪异:Java/C#/Python/PHP等语言可以轻松做到循环引用,无需使用类似的前置声明技巧。这不禁让人思考:C++为何必须要用前置声明才能化解?

    原因在于C++定义对象有两种方式:一种是A a形式,a即对象,调用成员变量或函数用.,对象在栈中分配;另一种是A* a,a是指针,调用成员变量或函数用->,其指向地址存储实际对象,对象在堆中分配。

    分配对象需要知道具体的内存大小,但以下形式我们不能确定类A和类B对象的大小:

    class A {

    B b;

    };

    class B {

    A a;

    };

    对于这个简单例子,你可以直观认为A和B占用同样的内存,例如1字节,但也可以是2字节,3字节等;根据内存对齐要求,一般是4字节,8字节等。无论哪种情况,编译器无法确定其对象占用内存,便会报错停止编译。所以你应该知道为什么C++永远不应该(不能)这样做了吧?

    那为何前置声明加指针的组合能解决循环引用问题的呢?因为正常情况下,数据类型指针在同一机器的编译器里占同样的内存。指针一般是4或者8个字节,对应32和64位指针。用了指针,即使有循环引用,类的大小也能轻易的确定下来。这也是Java/C#/Python/PHP等可以轻松循环引用的原因:这些语言中,对象变量其实都是指针,也意味着对象变量都是引用传递。

    如果不移除文件的相互包含,能否省去前置声明呢?答案是不能,原因如下:

    C++按照一个个编译单元(translation unit)进行编译,如果两个文件互相包含且没有#pragma once等包含保护措施,则会出现递归包含,编译器报错;

    如果两个头文件都有文件包含保护,编译A时会把B包含进来,但因为B包含了A,A中的包含保护生效,导致B文件内的内容实际未引入A,于是报B为未知符号的错误。

    总的来说,不管是否移除对方的头文件,前置声明都是必须的。实践中为了避免文件变动时重新编译的耗费,移除不必要的头文件是一个好习惯。

    以上就是详解C++ 前置声明的详细内容,更多关于C++ 前置声明的资料请关注我们其它相关文章!

    时间: 2020-09-08

    展开全文
  • 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration)。下面的程序中,带注释的那行就是类B的前置说明。这是必须的,因为类A中用到了类B,而类B的声明出现在类A的后面。如果没有类B的前置说明,下面的...

    在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration)。下面的程序中,带注释的那行就是类B的前置说明。这是必须的,因为类A中用到了类B,而类B的声明出现在类A的后面。如果没有类B的前置说明,下面的程序将不同通过编译,编译器将会给出类似“缺少类型说明符”这样的出错提示。

    代码一:

    // ForwardDeclaration.h

    #include

    using namespace std;

    class B; // 这是前置声明(Forward declaration)

    class A

    {

    private:

    B* b;

    public:

    A(B* b):b(b){}

    };

    class B

    {

    };

    // Main.cpp

    #include "ForwardDeclaration.h"

    int main(int argc, char** argv)

    {

    B* b = new B();

    A* a = new A(b);

    delete a;

    delete b;

    return 0;

    }

    上面程序可以顺利编译和运行(几乎没有做什么,也没有输出)。

    是不是有了前置说明就万事大吉了呢?我们看看下面的代码(带阴影部分的代码是新增加的):

    代码二:

    // ForwardDeclaration.h

    #include

    using namespace std;

    class B; // 这是前置声明(Forward declaration)

    class A

    {

    private:

    B* b;

    public:

    A(B* b):b(b){}

    void someMethod()

    {

    b->someMethod(); // (1)

    }

    };

    class B

    {

    public:

    void someMethod()

    {

    cout << "something happened..." << endl;

    }

    };

    // Main.cpp

    #include "ForwardDeclaration.h"

    int main(int argc, char** argv)

    {

    B* b = new B();

    A* a = new A(b);

    a->someMethod();    delete a;

    delete b;

    return 0;

    }

    一编译,发现代码(1)处出错。出错提示往往包括(不同的编译器给出的提示会有所不同):

    1. 使用了未定义的类型B;

    2. “->somemethod”的左边必须指向类/结构/联合/泛型类型

    原因:

    1. (1)处使用了类型B的定义,因为调用了类B中的一个成员函数。前置声明class B;仅仅声明了有一个B这样的类型,而并没有给出相关的定义,类B的相关定义,是在类A后面出现的,因此出现了编译错误;

    2. 代码一之所以能够通过编译,是因为其中仅仅用到B这个类型,并没有用到类B的定义。

    解决办法是什么?

    将类的声明和类的实现(即类的定义)分离。如下所示:

    // ForwardDeclaration.h 类的声明

    #include

    using namespace std;

    class B; // 这是前置声明(Forward declaration)

    class A

    {

    private:

    B* b;

    public:

    A(B* b);

    void someMethod();

    };

    class B

    {

    public:

    void someMethod();

    };

    // ForwardDeclaration.cpp 类的实现

    #include "ForwardDeclaration.h"

    A::A(B* b):b(b)

    {

    }

    void A::someMethod()

    {

    b->someMethod();

    }

    void B::someMethod()

    {

    cout << "something happened..." << endl;

    }

    // Main.cpp

    #include "ForwardDeclaration.h"

    int main(int argc, char** argv)

    {

    B* b = new B();

    A* a = new A(b);

    a->someMethod();

    delete a;

    delete b;

    return 0;

    }

    结论:

    前置声明只能作为指针或引用,不能定义类的对象,自然也就不能调用对象中的方法了。

    而且需要注意,如果将类A的成员变量B* b;改写成B& b;的话,必须要将b在A类的构造函数中,采用初始化列表的方式初始化,否则也会出错。关于这点

    见:

    展开全文
  • C++ 前置声明详解及实例【1】一般的前置函数声明见过最多的前置函数声明,基本格式代码如下:#include using namespace std;void fun(char ch, int *pValue, double dValue);void main(){int nValue = 100;double ...

    C++ 前置声明详解及实例

    【1】一般的前置函数声明

    见过最多的前置函数声明,基本格式代码如下:

    #include

    using namespace std;

    void fun(char ch, int *pValue, double dValue);

    void main()

    {

    int nValue = 100;

    double dValue = 111.22;

    fun('a', &nValue, dValue);

    system("pause");

    }

    void fun(char ch, int *pValue, double dValue)

    {

    return;

    }

    很好理解,不做赘述。

    【2】自定义类型的前置声明

    自定义类型的前置声明,由于编译器不知道类型的大小,所以不可以声明类型的对象。只可以利用类型声明指针和引用。

    代码如下:

    /*

    * 自定义类型前置声明

    */

    #include

    using namespace std;

    class B;

    class A

    {

    private:

    // 内置类型

    int m_nInt;

    int& m_nRInt;

    int* m_pInt;

    // 自定义类型

    // B b; // error!

    B* m_pB;

    B& m_b;

    public:

    A (B *pBPara = NULL) : m_nInt(100)

    , m_nRInt(m_nInt)

    , m_pInt(NULL)

    , m_pB(NULL)

    , m_b((NULL == pBPara) ? (*m_pB) : (*pBPara))

    {

    cout << "A()" << endl;

    }

    ~A()

    {

    cout << "~A()" << endl;

    }

    void funA()

    {

    // m_pB->doAnything(); // build error C2027: use of undefined type 'B'

    }

    };

    class B

    {

    private:

    int m_n;

    public:

    B (int n = 100) : m_n(n)

    {

    cout << "B()" << endl;

    }

    ~B()

    {

    cout << "~B()" << endl;

    }

    void doAnything()

    {

    cout << "B::anythig()" << endl;

    }

    };

    void main()

    {

    A objA;

    system("pause");

    }

    如上,利用前置类型的指针想调用其成员函数,会报编译错误!那么,肿么办?请看下文。

    【3】声明和实现分离

    代码如下,声明头文件:

    /*

    * TestForwardDeclar.h

    */

    #ifndef D_TESTFORWARDDECLAR_H_

    #define D_TESTFORWARDDECLAR_H_

    #include

    class B; // 前置声明自定义类型

    class A

    {

    private:

    // 内置类型

    int m_nInt;

    int& m_nRInt;

    int* m_pInt;

    // 自定义类型

    // B b; // error!

    B* m_pB;

    B& m_b;

    public:

    A (B *pBPara = NULL);

    ~A ();

    void funA();

    };

    class B

    {

    private:

    int m_n;

    public:

    B (int n = 100);

    ~B ();

    void doAnything();

    };

    #endif

    代码如下,定义文件:

    /*

    * TestForwardDeclar.cpp

    */

    #include "TestForwardDeclar.h"

    #include

    A::A (B *pBPara)

    : m_nInt(100)

    , m_nRInt(m_nInt)

    , m_pInt(NULL)

    , m_pB(NULL)

    , m_b((NULL == pBPara) ? (*m_pB) : (*pBPara))

    {

    std::cout << "A()" << std::endl;

    }

    A::~A()

    {

    std::cout << "~A()" << std::endl;

    }

    void A::funA()

    {

    m_pB->doAnything(); // 分开头文件和实现文件即可

    }

    B::B (int n) : m_n(n)

    {

    std::cout << "B()" << std::endl;

    }

    B::~B()

    {

    std::cout << "~B()" << std::endl;

    }

    void B::doAnything()

    {

    std::cout << "B::anythig()" << std::endl;

    }

    代码如下:测试文件:

    #include "TestForwardDeclar.h"

    void main()

    {

    A objA;

    }

    编译成功,运行结果是期望效果。

    【4】总结

    自定义类型前置声明时,只可以利用类型名声明指针和引用变量(谨记不可以声明对象或new 对象,均因为类型大小不确定,编译器无能为力)。

    若需要利用指针或引用调用前置类型的接口,必须按照声明和实现分离的方式进行编码。

    感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

    本文标题: C++ 前置声明详解及实例

    本文地址: http://www.cppcns.com/ruanjian/c/192802.html

    展开全文
  • 关于前置声明

    2020-04-20 18:31:39
    首先: 目前不推荐使用前置声明, 最好使用#include,即使你不太了解前置声明也是么有关系的。 https://www.zhihu.com/question/63201378 前置声明是什么? ...使用前置声明需要注意什么?...
  • C++ 前置声明

    2018-08-20 11:21:15
    C++ 前置声明
  • 背景:近期阅读代码的时候见过太多前置声明,于是整理了一些资料。前置声明 forward declaration就是类、模板、函数等等纯粹的声明,没有定义。使用场景: 1、用于两个头文件相互包含无法编译通过的问题; 2、用于...
  • C++前置声明

    2017-03-12 20:58:56
    C++前置声明
  • C++ 前置声明详解及实例 【1】一般的前置函数声明 见过最多的前置函数声明,基本格式代码如下: #include using namespace std; void fun(char ch, int *pValue, double dValue); void main() { int nValue = 100...
  • 前置声明

    2018-08-29 11:18:00
    // 这是前置声明(Forward declaration) class A { private: B* b; public: A(B* b):b(b){} }; class B { }; 如上:class B就是前置声明,因为在A中使用了B,所以要先声明 转载于:https://www...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,858
精华内容 743
关键字:

前置声明