编译时接口(非虚拟)
Compile-Time Interfaces (non-virtual)
如果你想为同一个对象使用不同的 public 接口,你可以使用虚拟基础 classes。但是那些有开销(内存和space)。
class View1 {
public:
int x;
}
class View2 : virtual public View1 {
public:
int y;
}
class View3 {
public:
int* a;
}
class Complex : virtual public View1, virtual public View2, virtual public View3 {
}
可以将对象转换为具有不同访问修饰符和相同大小的 class。这通常在纯 C 中使用结构来隐藏实现细节。但是这个解决方案本质上是不安全和未定义的行为,可能很难找到错误,因为优化器,如果它完成它的工作,可能无法很好地处理禁止的别名(相同的内存位置具有不同的名称)。当访问修饰符不同时,一些编译器可能会重新安排内存布局。像 dynamic_cast<>、reinterpret_cast<> 和 bit_cast<> 这样的强制转换只允许某些 classes.
class View1 {
public:
int x;
private:
int y;
int* a;
}
class Complex {
public:
int x;
int y;
int* a;
}
现在我至少找到了一种解决方案,其中一种使用 super classes 而不是 base classes 作为接口并且似乎是合法的。这是真的?有没有更简单的方法到达那里?
Complex.h:
#pragma once
#include <iostream>
class Complex {
protected:
Complex(int v) : x(0), y(0), a(new int) { *a = v };
~Complex() { std::cout << "Values before destruction: a: " << *a << ", x: " << x << ", y: " << y << std::endl; delete a; }
int* a;
int x;
int y;
};
View1.h:
#include "Complex.h"
class View1 : protected Complex {
protected:
View1(int v) : Complex(v) {}; // forward constructor with parameter
public:
using Complex::x;
};
View2.h:
#include "View1.h"
class View2 : protected View1 { // chain inheritance
protected:
View2(int v) : View1(v) {};
public:
using Complex::y;
};
View3.h:
#include "View2.h"
class View3 : protected View2 { // chain inheritance
protected:
View3(int v) : View2(v) {};
public:
using Complex::a;
};
Combined.h:
#include "View3.h"
class Combined : protected View3 {
public:
Combined(int v) : View3(v) {};
View3& view3() { return *static_cast<View3*>(this); }
View2& view2() { return *static_cast<View2*>(this); }
View1& view1() { return *static_cast<View1*>(this); }
};
test.cpp:
#include "Combined.h"
#include <iostream>
using namespace std;
int main() {
Combined object(6); // object is constructed
View1& v1 = object.view1(); // view1 only allows access to x
View2& v2 = object.view2(); // view2 only allows access to y
View3& v3 = object.view3(); // view3 only allows access to a
v1.x = 10;
v2.y = 13;
*v3.a = 15;
cout << sizeof(Combined) << endl; // typically only the data members = 16 on a 64-bit system (x: 4, y: 4, a: 8)
cout << addressof(object) << endl; // typically the object and all views have the same address, as only the access modifiers are changed
cout << addressof(v1) << endl;
cout << addressof(v2) << endl;
cout << addressof(v3) << endl;
return 0; // object is destructed and message shown
}
输出为:
16
0000000BF8EFFBE0
0000000BF8EFFBE0
0000000BF8EFFBE0
0000000BF8EFFBE0
Values before destruction: a: 15, x: 10, y: 13
视图只能看到它们各自的单个成员变量(其他受保护)。允许从 Combine 转换为基础 class(3 个视图)。对 Complex class 没有特殊要求,甚至没有标准布局或默认构造。
Complex class 包含所有成员和实现,但必须构造 Combined class 以便所有视图都是静态基础 classes。
在所示示例中,由于继承受到保护,因此只能使用 view1/2/3() 函数从 class 内部创建视图。可以进行 public 继承,但随后必须显式地使所有成员对受保护的视图不可见。并且可以看到视图的链接顺序。但好处是,可以直接从 Combined class 投射视图。这或许也可以通过 operator View1& conversion operators 来实现?
由于视图知道对象(=组合)的实际构造(动态)class,因此可以从视图指针进行销毁(此处未实现)。
这些视图仅适用于编译时已知对象的class,否则需要使用虚拟的常规解决方案。
静态(非开销)视图是否有更简单(合法)的方式,使用起来很舒服?
(总是可以回退到友元函数)
只需制作一个适配器:
#include <string>
#include <iostream>
// the original data class. Does not depend on adapters,
// thus has no reasons to be changed when a new adapter is added,
// completely SRP compliant
struct data
{
std::string str{"data"};
};
// this may be added in a completely separate header without the need
// to ever modify the data class
class view
{
public:
constexpr view(const data& ref)
: ref_(ref)
{}
const std::string& str() const
{
return ref_.str;
}
private:
const data& ref_;
};
// this function uses an interface, but doesn't own the resources
void print(view v)
{
std::cout << v.str();
}
int main()
{
// no heap allocation is needed for an adapter
print(data{"data"});
}
https://godbolt.org/z/hjEzMzYYs - 请参见 -O3
的示例
这假定您将视图用作接口,并且接口持有者不拥有底层数据。
适配器更干净,因为它们不会强制 view
类型依赖于 data
.
如果您想从适配器的类型签名中隐藏 data
,请使用类型擦除。
如果你想为同一个对象使用不同的 public 接口,你可以使用虚拟基础 classes。但是那些有开销(内存和space)。
class View1 {
public:
int x;
}
class View2 : virtual public View1 {
public:
int y;
}
class View3 {
public:
int* a;
}
class Complex : virtual public View1, virtual public View2, virtual public View3 {
}
可以将对象转换为具有不同访问修饰符和相同大小的 class。这通常在纯 C 中使用结构来隐藏实现细节。但是这个解决方案本质上是不安全和未定义的行为,可能很难找到错误,因为优化器,如果它完成它的工作,可能无法很好地处理禁止的别名(相同的内存位置具有不同的名称)。当访问修饰符不同时,一些编译器可能会重新安排内存布局。像 dynamic_cast<>、reinterpret_cast<> 和 bit_cast<> 这样的强制转换只允许某些 classes.
class View1 {
public:
int x;
private:
int y;
int* a;
}
class Complex {
public:
int x;
int y;
int* a;
}
现在我至少找到了一种解决方案,其中一种使用 super classes 而不是 base classes 作为接口并且似乎是合法的。这是真的?有没有更简单的方法到达那里?
Complex.h:
#pragma once
#include <iostream>
class Complex {
protected:
Complex(int v) : x(0), y(0), a(new int) { *a = v };
~Complex() { std::cout << "Values before destruction: a: " << *a << ", x: " << x << ", y: " << y << std::endl; delete a; }
int* a;
int x;
int y;
};
View1.h:
#include "Complex.h"
class View1 : protected Complex {
protected:
View1(int v) : Complex(v) {}; // forward constructor with parameter
public:
using Complex::x;
};
View2.h:
#include "View1.h"
class View2 : protected View1 { // chain inheritance
protected:
View2(int v) : View1(v) {};
public:
using Complex::y;
};
View3.h:
#include "View2.h"
class View3 : protected View2 { // chain inheritance
protected:
View3(int v) : View2(v) {};
public:
using Complex::a;
};
Combined.h:
#include "View3.h"
class Combined : protected View3 {
public:
Combined(int v) : View3(v) {};
View3& view3() { return *static_cast<View3*>(this); }
View2& view2() { return *static_cast<View2*>(this); }
View1& view1() { return *static_cast<View1*>(this); }
};
test.cpp:
#include "Combined.h"
#include <iostream>
using namespace std;
int main() {
Combined object(6); // object is constructed
View1& v1 = object.view1(); // view1 only allows access to x
View2& v2 = object.view2(); // view2 only allows access to y
View3& v3 = object.view3(); // view3 only allows access to a
v1.x = 10;
v2.y = 13;
*v3.a = 15;
cout << sizeof(Combined) << endl; // typically only the data members = 16 on a 64-bit system (x: 4, y: 4, a: 8)
cout << addressof(object) << endl; // typically the object and all views have the same address, as only the access modifiers are changed
cout << addressof(v1) << endl;
cout << addressof(v2) << endl;
cout << addressof(v3) << endl;
return 0; // object is destructed and message shown
}
输出为:
16
0000000BF8EFFBE0
0000000BF8EFFBE0
0000000BF8EFFBE0
0000000BF8EFFBE0
Values before destruction: a: 15, x: 10, y: 13
视图只能看到它们各自的单个成员变量(其他受保护)。允许从 Combine 转换为基础 class(3 个视图)。对 Complex class 没有特殊要求,甚至没有标准布局或默认构造。
Complex class 包含所有成员和实现,但必须构造 Combined class 以便所有视图都是静态基础 classes。
在所示示例中,由于继承受到保护,因此只能使用 view1/2/3() 函数从 class 内部创建视图。可以进行 public 继承,但随后必须显式地使所有成员对受保护的视图不可见。并且可以看到视图的链接顺序。但好处是,可以直接从 Combined class 投射视图。这或许也可以通过 operator View1& conversion operators 来实现?
由于视图知道对象(=组合)的实际构造(动态)class,因此可以从视图指针进行销毁(此处未实现)。
这些视图仅适用于编译时已知对象的class,否则需要使用虚拟的常规解决方案。
静态(非开销)视图是否有更简单(合法)的方式,使用起来很舒服?
(总是可以回退到友元函数)
只需制作一个适配器:
#include <string>
#include <iostream>
// the original data class. Does not depend on adapters,
// thus has no reasons to be changed when a new adapter is added,
// completely SRP compliant
struct data
{
std::string str{"data"};
};
// this may be added in a completely separate header without the need
// to ever modify the data class
class view
{
public:
constexpr view(const data& ref)
: ref_(ref)
{}
const std::string& str() const
{
return ref_.str;
}
private:
const data& ref_;
};
// this function uses an interface, but doesn't own the resources
void print(view v)
{
std::cout << v.str();
}
int main()
{
// no heap allocation is needed for an adapter
print(data{"data"});
}
https://godbolt.org/z/hjEzMzYYs - 请参见 -O3
这假定您将视图用作接口,并且接口持有者不拥有底层数据。
适配器更干净,因为它们不会强制 view
类型依赖于 data
.
如果您想从适配器的类型签名中隐藏 data
,请使用类型擦除。