C++进阶 降低文件间的编译依存关系(接口与实现解耦合)

  • Post author:
  • Post category:其他


问题背景

有时候会发现仅仅改动了某个类的一点实现(仅仅是几句代码),在编译时却发现要编译整个工程!特别是工程有点大时,编译要等很久很久。。。只为修改一个问题,时间都浪费在等待上了。为了避免这一问题,其实是可以通过巧妙的设计来避开文件间的依赖。


形成依赖的主要缘由:

头文件依赖,A类依赖B类,B类依赖C类,结果只改动C类的任何一个数据成员,A,B都得重编译。C++在编译期间,编译器需要知道对象的大小(才能够分配足够的内存空间),这就导致了上面的互相依赖的编译。


解决办法:

1.把类的实现细节隐藏在一个指针背后(指针的大小显然是已知的),指针指向实现细节的类,这样修改细节时不会引发大面积的依赖编译。

2.使用抽象基类。


解决方案一:指针背后的游戏

假设我们要实现一个Person的类

类的定义如下:

//Person.h文件

#pragma once
#include <iostream>
#include <string>
#include <memory>

//实现细节类的前置声明  注意:并没有用include引入该类的定义式,而是巧妙的用声明式替换它,这样头文件Preson对实现细节没有任何的依赖!
class PresonPri;  

class Person
{
public:
    Person();
    ~Person();

    std::string adder()const;
    std::string name()const;
    std::string date()const;

private:
    //这里使用智能指针,该指针用来指向类的实现细节
    std::tr1::shared_ptr<PresonPri> pImpl;   
};

在Person类的实现部分调用 他的细节类PresonPri来实现。

类的实现部分:

//Person.cpp 文件

#include "Person.h"
#include "PresonPri.h"

Person::Person()
:pImpl(new PresonPri())
{
}

Person::~Person()
{
}

std::string Person::adder() const
{
    return pImpl->_adder();
}

std::string Person::name() const
{
    return pImpl->_name();
}

std::string Person::date() const
{
    return pImpl->_date();
}

细节类

#pragma once
#include <string>
class PresonPri
{
public:

    PresonPri();
    ~PresonPri();

    std::string _adder()const;
    std::string _name()const;
    std::string _date()const;

private:
    std::string name;
    std::string adder;
    std::string date;
};


----------

#include "PresonPri.h"

PresonPri::PresonPri()
{
    adder = "地球";
    name = "xf";
    date = "2020-1-12";
}

PresonPri::~PresonPri()
{

}

std::string PresonPri::_adder() const
{
    return adder;
}

std::string PresonPri::_name() const
{
    return name;
}

std::string PresonPri::_date() const
{
    return date;
}
//main.cpp

#include "Person.h"

int main()
{
    using namespace std;
    Person per;
    cout << per.adder() <<per.date() <<per.name() << endl;
    getchar();
}

这样设计,任何引用 person.h的代码,在该类的实现改变时都不会被重新编译,毕竟不管你怎么该实现的细节,Person类的头文件始终没有变,又有什么理由重新编译浪费时间呢。

可以简单的看看文件依赖图

这里写图片描述

main.cpp仅仅依赖与 person.h

+——–

总结:这种分离的关键在于,用“声明的依存性”替换“定义的依存性”。其本质是:实现中让头文件尽可能的自我满足。

  • 如果使用对象的引用或者指针可以完成任务,就不要使用对象。因为只靠一个类型声明式就可以定义指向该类型的引用或者指针;但如果要定义某种类型的对象,就必须要用到该类型的定义式了。
  • 尽量用class的声明式替换class的定义式。注意:当你声明一个函数而用到一个class时,你并不需要该class的定义式,构造函数也不例外,因此可以这样改进之前的Person类:
//perfwd.h
#pragma once
class PresonPri;
class Data;
class Adder;


----------


//person.h
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "perfwd.h"
class Person
{
public:
    Person(Adder & adder,Data &data,std::string &name);  //有参构造函数
    ~Person();

    std::string adder()const;
    std::string name()const;
    std::string data()const;

private:
    std::tr1::shared_ptr<PresonPri> pImpl;
};


----------
其他文件略......

----------

//main.cpp
#include "Person.h"
#include "Data.h"
#include "Adder.h"

int main()
{
    Adder adder;
    Data data;
    std::string name;

    Person per(adder,data,name);
    std::cout << per.data() << std::endl;
    getchar();
    return 0;
}

解决方案二:使用抽象类

抽象类是描述接口的,通常是没有数据成员的,也没有构造函数,只有一个virtual析构函数以及一组接口函数

抽象类一般是不会改动的,客户也只能创建他的指针和引用。那么如何创建他的实例?可以使用一种工厂方法,返回他的实例指针。当然,该指针是指向他的具体类的。(父类的指针可以指向其子类)

贴一下代码:

//Person.h文件

#pragma once
#include <iostream>
#include <string>
#include <memory>
class Person
{
public:
    ~Person();

    static std::tr1::shared_ptr<Person> creat();
    virtual std::string adder()const=0;
    virtual std::string name()const=0;
    virtual std::string date()const=0;
};


----------
//Person.cpp文件

#include "Person.h"
#include "PresonPri.h"


Person::~Person()
{

}

std::tr1::shared_ptr<Person>  Person::creat()
{
    return std::tr1::shared_ptr<Person>(new PresonPri());
}


----------
//PresonPri.h文件

#pragma once
#include "Person.h"
#include <string>
class PresonPri:public Person
{
public:

    PresonPri();
    ~PresonPri();

    std::string adder()const;
    std::string name()const;
    std::string date()const;

private:
    std::string m_name;
    std::string m_adder;
    std::string m_date;
};


----------
//PresonPri.cpp文件

#include "PresonPri.h"
PresonPri::PresonPri()
{
    m_adder = "地球";
    m_name = "xf";
    m_date = "2020-1-12";
}

PresonPri::~PresonPri()
{

}

std::string PresonPri::adder() const
{
    return m_adder;
}

std::string PresonPri::name() const
{
    return m_name;
}

std::string PresonPri::date() const
{
    return m_date;
}


----------
//main.cpp文件

#include "Person.h"
int main()
{
    using namespace std;
    std::tr1::shared_ptr<Person> per(Person::creat());
    cout << per->adder() <<per->date() <<per->name() << endl;
    getchar();
}

总结:这两种方法都是通过解除接口和实现之间的耦合关系,从而降低文件间的编译依存性。


我的个人网站

http://www.breeziness.cn/


我的CSDN

http://blog.csdn.net/qq_33775402

转载请注明出处 小风code www.breeziness.cn



版权声明:本文为qq_33775402原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。