您正在寻找 PIMPL 成语。 “PIMPL”是“指向实现的指针”的缩写。这个想法是,以指针间接为代价,您将实现数据和私有方法隐藏在内部类中,其定义对于 API 使用者不透明。
如果您需要提供 ABI 稳定性,这种方法特别有效。
Herb Sutter 对此有很好的 GOTW:https://herbsutter.com/gotw/_100/
这是一个与您的代码非常接近的完整示例:
$ tree
.
├── CMakeLists.txt
├── include
│ └── world.h
├── main.cpp
└── src
├── world.cpp
└── world_priv.h
在./include/world.h(公共标头)中
#ifndef WORLD_H
#define WORLD_H
#include <memory>
#include <string>
#include "world_export.h"
namespace ex {
class World {
public:
WORLD_EXPORT World(std::string name);
WORLD_EXPORT ~World() /* = default */;
void WORLD_EXPORT say_hello();
private:
class Impl;
std::unique_ptr<Impl> pImpl;
};
} // namespace ex
#endif
在./src/world_priv.h:
#ifndef WORLD_PRIV_H
#define WORLD_PRIV_H
#include "world.h"
namespace ex {
class World::Impl {
public:
Impl(std::string name) : name(std::move(name)) {}
void say_hello();
void hidden();
private:
std::string name;
};
} // namespace ex
#endif
在./src/world.cpp:
#include <iostream>
#include "world_priv.h"
namespace ex {
World::World(std::string name)
: pImpl(std::make_unique<Impl>(std::move(name))) {}
World::~World() = default;
void World::say_hello() { pImpl->say_hello(); }
void World::Impl::say_hello() {
std::cout << "Hello, World from " << name << "\n";
}
void World::Impl::hidden() { std::cout << "Not exported" << std::endl; }
} // namespace ex
在main.cpp:
#include <world.h>
int main() {
ex::World w("Earth");
w.say_hello();
}
最后,这是构建:
cmake_minimum_required(VERSION 3.21)
project(pimpl_example)
option(BUILD_SHARED_LIBS "Build world as shared rather than static" ON)
# Library
include(GenerateExportHeader)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
add_library(world src/world.cpp src/world_priv.h include/world.h)
add_library(world::world ALIAS world)
target_include_directories(
world PRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>"
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
)
generate_export_header(world EXPORT_FILE_NAME include/world_export.h)
target_compile_definitions(
world PUBLIC "$<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:WORLD_STATIC_DEFINE>")
target_include_directories(
world PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
# Application
add_executable(app main.cpp)
target_link_libraries(app PRIVATE world::world)
这不包括安装规则或任何东西,但它已准备好编写。
构建它:
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
...
$ cmake --build build
$ ./build/app
Hello, World from Earth
$ $ nm ./build/libworld.so | c++filt | grep ' T ' | uniq
0000000000001460 T ex::World::say_hello()
00000000000012e0 T ex::World::World(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
00000000000013c0 T ex::World::~World()
可以看到库只导出了ex::World的API。所有私人细节都隐藏在代码和库本身中。
在 Windows 上:
>dumpbin /EXPORTS build\world.dll
Microsoft (R) COFF/PE Dumper Version 14.28.29915.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file build\world.dll
File Type: DLL
Section contains the following exports for world.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
3 number of functions
3 number of names
ordinal hint RVA name
1 0 00001390 ??0World@ex@@QEAA@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z
2 1 00001550 ??1World@ex@@QEAA@XZ
3 2 000015D0 ?say_hello@World@ex@@QEAAXXZ
Summary
1000 .data
1000 .pdata
2000 .rdata
1000 .reloc
1000 .rsrc
2000 .text