我可以为 C++ class 提供一个不完整的 header 来隐藏实现细节吗?
Can I provide an incomplete header for a C++ class to hide the implementation details?
我想将 class 实现分成三个部分,以避免用户需要处理实现细节,例如,我用来实现功能的库:
impl.cpp
#include <api.h>
#include <impl.h>
Class::Class() {
init();
}
Class::init() {
myData = SomeLibrary::Type(42);
}
Class::doSomething() {
myData.doSomething();
}
impl.h
#include <somelibrary.h>
class Class {
public:
Class();
init();
doSomething();
private:
SomeLibary::Type myData;
}
api.h
class Class {
Class();
doSomething();
}
问题是,不允许我为 class 定义重新定义 headers。当我仅在 api.h
中定义 Class()
和 doSomething()
时,这也不起作用。
一个可能的选择是定义api.h
并且根本不在项目中使用它,而是安装它(并且不安装impl.h
)。
明显的缺点是,我需要确保 api.h
和 impl.h
中的常用方法始终具有相同的签名,否则使用该库的程序将出现链接器错误,即编译库时我无法预测。
但是这种方法是否有效,或者我会遇到其他问题(例如指向 class 成员的错误指针或类似问题),因为 obj 文件与 header 不匹配?
简短的回答是"No!"
原因:any/all 'client' 需要使用您的 Class
class 的项目必须具有 完整声明 class,以便编译器可以正确确定成员变量的偏移量等内容。
private
成员的使用很好 - 客户端程序将无法更改它们 - 正如您当前的实现一样,[=54= 中仅提供了成员函数的最简短概述],所有实际定义都在您的(私有)源文件中。
一种可能的解决方法是声明一个 指针 指向 Class
中的嵌套 class,这个嵌套的 class 只是在共享的 header: class NestedClass
中声明,然后你可以在你的实现中用那个嵌套的 class 指针做你喜欢的事情。您通常会将嵌套的 class 指针设为 private
成员;此外,由于共享 header 中未给出其定义,因此 'client' 项目试图访问该 class(作为指针除外)的任何尝试都将是编译器错误。
这是一个可能的代码分解(可能不是 error-free,但是,因为它很快 type-up):
// impl.h
struct MyInternal; // An 'opaque' structure - the definition is For Your Eyes Only
class Class {
public:
Class();
init();
doSomething();
private:
MyInternal* hidden; // CLient never needs to access this! Compiler error if attempted.
}
// impl.cpp
#include <api.h>
#include <impl.h>
struct MyInternal {
SomeLibrary::Type myData;
};
Class::Class() {
init();
}
Class::init() {
hidden = new MyInternal; // MUCH BETTER TO USE unique_ptr, or some other STL.
hidden->myData = SomeLibrary::Type(42);
}
Class::doSomething() {
hidden->myData.doSomething();
}
注意:正如我在代码注释中暗示的那样,使用 std::unique_ptr<MyInternal> hidden
会更好。但是,这将需要您在 Class
中为析构函数、赋值运算符和其他(移动运算符?复制构造函数?)提供 显式 定义,因为这些将需要访问MyInternal
结构的完整定义。
私有实现 (PIMPL) 惯用语可以帮助您解决这个问题。它可能会产生 2 header 和 2 个源文件,而不是 2 和 1。有一个我实际上没有尝试编译的愚蠢示例:
api.h
#pragma once
#include <memory>
struct foo_impl;
struct foo {
int do_something(int argument);
private:
std::unique_ptr<foo_impl> impl;
}
api.c
#include "api.h"
#include "impl.h"
int foo::do_something(int a) { return impl->do_something(); }
impl.h
#pragma once
#include <iostream>
struct foo_impl {
foo_impl();
~foo_impl();
int do_something(int);
int initialize_b();
private:
int b;
};
impl.c
#include <iostream>
foo_impl::foo_impl() : b(initialize_b()} { }
foo_impl::~foo_impl() = default;
int foo_impl::do_something(int a) { return a+b++; }
int foo_impl::initialize_b() { ... }
foo_impl
可以有它需要的任何方法,因为 foo
的 header(API)是用户将看到的所有内容。编译器编译 foo
所需的全部知识是有一个指针作为数据成员,因此它可以正确调整大小 foo
。
我想将 class 实现分成三个部分,以避免用户需要处理实现细节,例如,我用来实现功能的库:
impl.cpp
#include <api.h>
#include <impl.h>
Class::Class() {
init();
}
Class::init() {
myData = SomeLibrary::Type(42);
}
Class::doSomething() {
myData.doSomething();
}
impl.h
#include <somelibrary.h>
class Class {
public:
Class();
init();
doSomething();
private:
SomeLibary::Type myData;
}
api.h
class Class {
Class();
doSomething();
}
问题是,不允许我为 class 定义重新定义 headers。当我仅在 api.h
中定义 Class()
和 doSomething()
时,这也不起作用。
一个可能的选择是定义api.h
并且根本不在项目中使用它,而是安装它(并且不安装impl.h
)。
明显的缺点是,我需要确保 api.h
和 impl.h
中的常用方法始终具有相同的签名,否则使用该库的程序将出现链接器错误,即编译库时我无法预测。
但是这种方法是否有效,或者我会遇到其他问题(例如指向 class 成员的错误指针或类似问题),因为 obj 文件与 header 不匹配?
简短的回答是"No!"
原因:any/all 'client' 需要使用您的 Class
class 的项目必须具有 完整声明 class,以便编译器可以正确确定成员变量的偏移量等内容。
private
成员的使用很好 - 客户端程序将无法更改它们 - 正如您当前的实现一样,[=54= 中仅提供了成员函数的最简短概述],所有实际定义都在您的(私有)源文件中。
一种可能的解决方法是声明一个 指针 指向 Class
中的嵌套 class,这个嵌套的 class 只是在共享的 header: class NestedClass
中声明,然后你可以在你的实现中用那个嵌套的 class 指针做你喜欢的事情。您通常会将嵌套的 class 指针设为 private
成员;此外,由于共享 header 中未给出其定义,因此 'client' 项目试图访问该 class(作为指针除外)的任何尝试都将是编译器错误。
这是一个可能的代码分解(可能不是 error-free,但是,因为它很快 type-up):
// impl.h
struct MyInternal; // An 'opaque' structure - the definition is For Your Eyes Only
class Class {
public:
Class();
init();
doSomething();
private:
MyInternal* hidden; // CLient never needs to access this! Compiler error if attempted.
}
// impl.cpp
#include <api.h>
#include <impl.h>
struct MyInternal {
SomeLibrary::Type myData;
};
Class::Class() {
init();
}
Class::init() {
hidden = new MyInternal; // MUCH BETTER TO USE unique_ptr, or some other STL.
hidden->myData = SomeLibrary::Type(42);
}
Class::doSomething() {
hidden->myData.doSomething();
}
注意:正如我在代码注释中暗示的那样,使用 std::unique_ptr<MyInternal> hidden
会更好。但是,这将需要您在 Class
中为析构函数、赋值运算符和其他(移动运算符?复制构造函数?)提供 显式 定义,因为这些将需要访问MyInternal
结构的完整定义。
私有实现 (PIMPL) 惯用语可以帮助您解决这个问题。它可能会产生 2 header 和 2 个源文件,而不是 2 和 1。有一个我实际上没有尝试编译的愚蠢示例:
api.h
#pragma once
#include <memory>
struct foo_impl;
struct foo {
int do_something(int argument);
private:
std::unique_ptr<foo_impl> impl;
}
api.c
#include "api.h"
#include "impl.h"
int foo::do_something(int a) { return impl->do_something(); }
impl.h
#pragma once
#include <iostream>
struct foo_impl {
foo_impl();
~foo_impl();
int do_something(int);
int initialize_b();
private:
int b;
};
impl.c
#include <iostream>
foo_impl::foo_impl() : b(initialize_b()} { }
foo_impl::~foo_impl() = default;
int foo_impl::do_something(int a) { return a+b++; }
int foo_impl::initialize_b() { ... }
foo_impl
可以有它需要的任何方法,因为 foo
的 header(API)是用户将看到的所有内容。编译器编译 foo
所需的全部知识是有一个指针作为数据成员,因此它可以正确调整大小 foo
。