循环包含在C+中隐藏实现细节的技巧+;标头档 我试图找到一种干净的方法来分离大型项目中C++头文件中的实现细节,以实现更好的信息隐藏和减少构建时间。C++的问题是每次更改私有成员声明时,必须重建依赖类。
这是我想出的解决办法。有什么好处吗 基本思想是在头文件中有条件地包含cpp文件的一部分。此部分包含实现声明,仅当实现文件包含头时才包含。对于外部类,此详细信息将从标题中排除。所以客户端和实现可以看到头文件的两个不同版本。内部声明更改不会影响客户端(不编译依赖类),并且标头不会包含私有详细信息 以下是实施方案: 标题循环包含在C+中隐藏实现细节的技巧+;标头档 我试图找到一种干净的方法来分离大型项目中C++头文件中的实现细节,以实现更好的信息隐藏和减少构建时间。C++的问题是每次更改私有成员声明时,必须重建依赖类。,c++,include,implementation,C++,Include,Implementation,这是我想出的解决办法。有什么好处吗 基本思想是在头文件中有条件地包含cpp文件的一部分。此部分包含实现声明,仅当实现文件包含头时才包含。对于外部类,此详细信息将从标题中排除。所以客户端和实现可以看到头文件的两个不同版本。内部声明更改不会影响客户端(不编译依赖类),并且标头不会包含私有详细信息 以下是实施方案: 标题 #pragma once class Dependency { public: Dependency(void); ~Dependency(void); v
#pragma once
class Dependency
{
public:
Dependency(void);
~Dependency(void);
void Proc(void);
//PRIVATE Implementaion details stays private
#ifdef Dependency_PRIVATE_IMPELEMENTATION
#define Dependency_PRIVATE_MODE 1
#include "Dependency.cpp"
#undef Dependency_PRIVATE_MODE
#endif
};
CPP
#define Dependency_PRIVATE_IMPELEMENTATION
#include "Dependency.h"
#undef Dependency_PRIVATE_IMPELEMENTATION
#ifdef Dependency_PRIVATE_MODE
private:
int _privateData;
#else
#include <iostream>
Dependency::Dependency(void)
{
//This line causes a runtime exception, see client
Dependency::_privateData = 0;
}
Dependency::~Dependency(void)
{
}
void Dependency::Proc(void)
{
std::cout << "Shiny happy functions.";
}
#endif
看看这个(又名pImpl)
该模式通常用于类希望隐藏内部实现的情况,但也有一个好处,即对内部和私有结构的更改不会创建重新编译,因为二进制调用兼容性得到了维护
以其他方式执行此操作的问题是,当您更改类定义中的任何内容时,可能无法保持二进制兼容性,因此所有软件都必须重新编译
看起来您的解决方案正是试图做到这一点,但是您应该使用(void*)而不是int,以确保软件在不同平台上的32位和64位编译器上正确编译,并且只使用cook book中不透明指针的示例。这不起作用。如果在private
.cpp
文件中向类添加任何内容,则该类的用户将看到与实现所认为的不同的类
这是不合法的,在很多情况下都不起作用。KDE有一篇关于C++中可以改变的和不能改变的ABI兼容性的文章。如果你用你的“隐藏”实现破坏了其中的任何一个,你将破坏用户
请查看,寻找一种相当常见的方法来完成您试图实现的目标。这行不通。您可以很容易地看到它,因为实现和客户端的
sizeof(Dependency)
是不同的。客户端基本上看到了一个不同的类,访问了内存中的不同位置,一切都搞砸了
不幸的是,如果更改类,则无法阻止重建依赖文件。但是,您可以像这样隐藏实现细节:
标题:
cpp文件
#包括
类私有数据
{
/*你的数据在这里*/
};
依赖项::依赖项()
{
pd=新的私有数据;
}
依赖项::~Dependency()
{
如果(pd)
删除pd;
}
void依赖项::Proc()
{
/*你的代码*/
}
请注意,这不是供您复制粘贴的。这只是给你一个想法。此用法可能暗示缺少错误检查或代码。其中一个是防止浅拷贝的拷贝构造函数。这是一个非常有趣的问题,真的。管理依赖关系对于大型项目非常重要,因为构建时间的增加甚至会使最简单的更改变得令人望而生畏。。。当它发生的时候,人们会试图破解它,以避免死亡的重建(tm) 不幸的是,它不起作用 该标准明确规定,出现在不同翻译单位(大致为文件)中的类定义应遵守一个定义规则(见§3.2一个定义规则[basic.def.odr]) 为什么? 在某种程度上,问题是阻抗的问题。类的定义包含关于类ABI(应用程序二进制接口)的信息,最显著的是,此类类在内存中的布局方式。如果同一个类在不同的翻译单元中有不同的布局,那么当把它放在一起时,它就不起作用了。好像一个屠人说德语,另一个说韩语。他们可能试图说同样的话,他们只是不理解对方 那么 有几种方法可以管理依赖关系。主要思想是,您应该尽可能努力提供“轻”标题:
- 包括尽可能少的东西。您可以向前声明:显示为参数或函数返回声明的类型,通过引用或指针传递但未使用的类型
- 隐藏实现细节
#include "project/a.hpp" // defines class A
#include "project/b.hpp" // defines class B
#include "project/c.hpp" // defines class C
#include "project/d.hpp" // defines class D
#include "project/e.hpp" // defines class E
namespace project {
class MyClass {
public:
explicit MyClass(D const& d): _a(d.a()), _b(d.b()), _c(d.c()) {}
MyClass(A a, B& b, C* c): _a(a), _b(b), _c(c) {}
E e() const;
private:
A _a;
B& _b;
C* _c;
}; // class MyClass
} // namespace project
这个收割台拉进了另外5个收割台,但实际上需要多少个
是必需的,因为a.hpp
\u类型
是类的属性a
不是必需的,我们只参考了b.hpp
b
不是必需的,我们只有一个指向c.hpp
c
是必需的,我们调用d.hpp
d
不是必需的,它只显示为返回e.hpp
#include "project/a.hpp" // defines class A
#include "project/d.hpp" // defines class D
namespace project { class B; }
namespace project { class C; }
namespace project { class E; }
namespace project {
class MyClass {
public:
explicit MyClass(D const& d): _a(d.a()), _b(d.b()), _c(d.c()) {}
MyClass(A a, B& b, C* c): _a(a), _b(b), _c(c) {}
E e() const;
private:
A _a;
B& _b;
C* _c;
}; // class MyClass
} // namespace project
我们能做得更好吗
首先,我们可以看到,我们只在类的构造函数中调用D
上的方法,如果我们将D
的定义移出标题,并将其放入.cpp
文件中,那么我们就不再需要包含D.hpp
// no need to illustrate right now ;)
但是。。。那A
呢
有可能会“作弊”,因为只持有一个指针并不需要一个完整的定义。这就是所谓的指向实现的指针习惯用法(简称pimpl)。它权衡了运行时与较轻的依赖关系,并为类增加了一些复杂性。下面是一个演示:
#include <memory> // don't really worry about std headers,
// they are pulled in at one time or another anyway
namespace project { class A; }
namespace project { class B; }
namespace project { class C; }
namespace project { class D; }
namespace project { class E; }
namespace project {
class MyClass {
public:
explicit MyClass(D const& d);
MyClass(A a, B& b, C* c);
~MyClass(); // required to be in the source file now
// because for deleting Impl,
// the std::unique_ptr needs its definition
E e() const;
private:
struct Impl;
std::unique_ptr<Impl> _impl;
}; // class MyClass
} // namespace project
好吧,那就是低沉和坚韧。进一步阅读:
#include "project/a.hpp" // defines class A
#include "project/d.hpp" // defines class D
namespace project { class B; }
namespace project { class C; }
namespace project { class E; }
namespace project {
class MyClass {
public:
explicit MyClass(D const& d): _a(d.a()), _b(d.b()), _c(d.c()) {}
MyClass(A a, B& b, C* c): _a(a), _b(b), _c(c) {}
E e() const;
private:
A _a;
B& _b;
C* _c;
}; // class MyClass
} // namespace project
// no need to illustrate right now ;)
#include <memory> // don't really worry about std headers,
// they are pulled in at one time or another anyway
namespace project { class A; }
namespace project { class B; }
namespace project { class C; }
namespace project { class D; }
namespace project { class E; }
namespace project {
class MyClass {
public:
explicit MyClass(D const& d);
MyClass(A a, B& b, C* c);
~MyClass(); // required to be in the source file now
// because for deleting Impl,
// the std::unique_ptr needs its definition
E e() const;
private:
struct Impl;
std::unique_ptr<Impl> _impl;
}; // class MyClass
} // namespace project
#include "project/myClass.hpp" // good practice to have the header included first
// as it asserts the header is free-standing
#include "project/a.hpp"
#include "project/b.hpp"
#include "project/c.hpp"
#include "project/d.hpp"
#include "project/e.hpp"
struct MyClass::Impl {
Impl(A a, B& b, C* c): _a(a), _b(b), _c(c) {}
A _a;
B& _b;
C* _c;
};
MyClass::MyClass(D const& d): _impl(new Impl(d.a(), d.b(), d.c())) {}
MyClass::MyClass(A a, B& b, C* c): _impl(new Impl(a, b, c)) {}
MyClass::~MyClass() {} // nothing to do here, it'll be automatic
E MyClass::e() { /* ... */ }