给定一个C++嵌套的私有结构类型,是否有从文件范围静态函数访问它的策略
Given a C++ nested private struct type, are there tactics for accessing it from a file scope static function?
有人能描述一下作者John Lakos在下面引用的精确编码策略吗?
约翰·拉科斯:
更具争议的是,通常最好有一个结构的两个副本——例如,一个嵌套/私有在.h文件中(内联方法和朋友可以访问),另一个在.cpp文件中的文件范围内(文件范围静态函数可以访问)——并确保它们在本地保持同步,而不是用实现细节污染全局空间。
这句话出现在较新的Lakos大部头《大规模C++》中。
(这本书是对与Lakos早期著作《大规模C++软件设计》相同主题的更新处理)
报价见第9页第0.2节。
如果后面的章节能弄清楚拉科斯在描述什么,我会回到这里并给出答案。
与此同时,我开始着迷于理解这句话,我试图扫描这本书的目录和索引以寻找进一步的线索,但尚未得到答案。
以下是我自己的示例代码,用于我想象将通过神秘策略解决的问题:
// THE HEADER
namespace project
{
class OuterComponent
{
public:
inline int GetFoo()
{
return m_inner.foo;
}
int GetBar();
private:
struct InnerComponent
{
int foo = 0;
int bar = 0;
};
InnerComponent m_inner;
};
} // namespace project
连同:
// THE CPP IMPLEMENTATION FILE
namespace project
{
namespace
{
/*
MYSTERY:
Per the book quotation, I can somehow add a "copy of InnerComponent" here?
And "make sure to keep them in sync locally"?
*/
// COMPILATION ERROR (see below)
int FileScopeComputation( OuterComponent::InnerComponent i )
{
return i.bar - 3;
}
} // namespace
int OuterComponent::GetBar()
{
return FileScopeComputation( m_inner );
}
} // namespace project
当然,以上内容不会编译。
错误将类似于:
error: ‘struct project::OuterComponent::InnerComponent’ is private within this context
int FileScopeComputation( OuterComponent::InnerComponent i )
^~~~~~~~~~~~~~
名为FileScopeComputation
的免费函数无法访问InnerComponent
,原因我很清楚。
将上述代码与图书报价关联
回到拉科斯的名言,我认为FileScopeComputation
是该名言所称的"文件范围静态函数">。
一个";明显的";编译代码的解决方案是移动InnerComponent
,使其在OuterComponent
的public
部分中声明。
然而,我把我的";明显的";有罪的解决方案(根据报价)";通过实施细节[污染]全球空间">
因此,我的代码似乎捕捉到了两者:(a)间接策略的目标和(b)一个潜在解决方案的不必要污染。那么,替代方案是什么呢?
请回答其中一个或两个:
(1) 是否有一种方法可以在cpp文件中制作struct InnerComponent
的另一个副本,使得字段OuterComponent::m_inner
保持私有,类型OuterComponent::InnerComponent
也保持私有,并且函数FileScopeComputation
不知何故有某种方法可以做一些事情";等效";访问InnerComponent
实例上的数据?
我试过一些奇怪的选角方法,但没有什么值得在书中推荐的。同时,根据我的经验,拉科斯在书中推荐的东西都很值得推荐。
(2) 我是不是完全误读了这句话适用于什么样的场景?如果是这样的话,那么这句话实际上指的是什么C++软件设计问题?还有什么问题涉及";一个结构的两个副本。。。h文件中的一个。。。。cpp文件中的另一个"?
更新:
基于dfri敏锐的回答,上述代码确实可以在最小的更改下进行编译,并且:
- 字段
OuterComponent::m_inner
保持私有 - 类型CCD_ 13也保持私有
- 而函数CCD_ 14具有访问CCD_
标题代码获得一行额外的代码:
...
private:
struct InnerUtil; // <-- this line was ADDED. all else is same as above.
struct InnerComponent
{
int foo = 0;
int bar = 0;
};
InnerComponent m_inner;
};
cpp文件代码变为:
struct OuterComponent::InnerUtil
{
static int FileScopeComputation( OuterComponent::InnerComponent i )
{
return i.bar - 3;
}
};
int OuterComponent::GetBar()
{
return InnerUtil::FileScopeComputation( m_inner );
}
因子分解模式(Lakos)
Lakos可能实际上是指公共API委托调用的单独命名的类型。在引用和Lakos Factoring模式("Imp和ImpUtil模式")之间有一种相似的感觉;ImpUtil";部分
struct A {};
struct B {};
struct C {};
// widgetutil.h
// (definitions placed in widgetutil.cpp)
struct WidgetUtil {
// "Keep API in sync with Widget::foo".
static void foo(const A& b, const B& c, const C& a) {
// All implementation here in the util.
(void)a; (void)b; (void)c;
}
// "Keep API in sync with Widget::bar".
static void bar(const B& b, const C& c) {
// All implementation here in the util.
(void)b; (void)c;
}
};
// widget.h
// includes "widgetutil.h"
// Public-facing API
// (Ignoring the Imp pattern, only using the Util pattern).
struct Widget {
void foo(const A& a, const B& b) const {
// Only delegation to the util.
WidgetUtil::foo(a, b, c_);
}
void bar(const B& b) const {
// Only delegation to the util.
WidgetUtil::bar(b, c_);
}
private:
C c_{};
};
int main() {
const Widget w;
w.foo(A{}, B{}); // --> WidgetUtil::foo
}
这是一种将实现细节(WidgetUtil
)与公开的API(Widget
)分离的方法,也有助于测试:
- 实现详细介绍了CCD_ 18单元测试中隔离测试的单元
- 可以在不必担心
WidgetUtil
成员中的副作用的情况下执行Widget
的测试,因为后者可以在Widget
中使用静态依赖注入来完全模拟(使其成为类模板)
如果我们回顾Lakos的报价(在OP中),WidgetUtil
也可以作为文件-操作类放置在widget.cpp
源文件中,隐藏在公共API之外。这将意味着更多的封装,但不会像上述分离那样促进测试。
最后,请注意,使Widget
成为类模板并不一定意味着用定义(需要在包括和实例化Widget
的每个TU中编译)污染widget.h
的用户的翻译单元。由于Widget
仅作为类模板以便于测试,因此它的产品实现将只使用单个实例化,即注入产品WidgetUtil
。这意味着可以将Widget
类模板成员函数的定义与其声明分离,就像非模板类一样,并在widget.cpp
中显式实例化Widget<WidgetUtil>
专用化。例如,使用以下方法:
- 分离类模板的头文件中的类模板
Widget
定义转换为单独的CCD_ -timpl.h
头文件又包含在相关联的.cpp
文件中,该文件又包含用于生产中使用的单个类型模板参数(即WidgetUtil
)的Widget
类模板的显式实例化
测试可以类似地包括-timpl.h
头,并使用模拟的util类实例化Widget
类模板。
- 在C++中使用没有合作伙伴Class/Cpp文件的头文件是否实用
- 在头文件上包含 cpp 文件是否有缺点?
- 如何检查文件是否已存在于 fstream C++中
- 预处理的 C/C++ 文件是否特定于计算机?
- 如何使用 jpeglib.h 获取文件是否为 JPEG 类型
- 如何在 Linux 下使用 c++ 知道文件是否被其他进程使用?
- 使用 g++ 编译时列出所有 cpp/cc 文件是否正常?
- 如何检查文件是否复制成功?(便携式解决方案)C++
- 如何检查 win32 exe 文件是否已运行?(使用 win32 API 代码)
- 如何检查每个头文件是否包含必需的包含文件?
- 为许多类可能需要的所有常量变量制作独立的头文件是否是一种很好的做法?
- 使用 CodeBlocks + GCC 构建的可执行文件是否可以在任何 Windows 上运行?
- 如何检查给定文件是否是C++中的有效视频文件?
- 关于使用ifstream在C 中确定文件是否存在的问题
- #include 包含的头文件已包含的头文件是否常见?
- 如何测试文件是否被锁定和/或只读而不打开?
- 检查文件是否具有Windows c ++的可执行权限
- 检查给定的多个文件是否存在
- .txt文件是否已关闭
- 如果给定正确的运行时库,x86 可执行文件是否可以在任何 x86 平台上运行