If you want to have different public interfaces for one and the same object, you can use virtual base classes. But those have overhead (memory and 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 {
}
One could cast the object to a class with different access modifiers and same size. This is often done in plain C with structs to hide implementation details. But this solution is inherently unsafe and undefined behaviour with possibly very hard-to-find bugs, as the optimizer, if it does its job, may not handle forbidden aliasing (the same memory location having different names) well. And some compilers may rearrange the memory layout, when the access modifiers are different. Casts like dynamic_cast<>, reinterpret_cast<> and bit_cast<> are only allowed for certain classes.
class View1 {
public:
int x;
private:
int y;
int* a;
}
class Complex {
public:
int x;
int y;
int* a;
}
Now I found at least one solution, which kind of uses super classes instead of base classes as interface and seems to be legal. Is this true? Is there an easier way to get there?
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
}
The output is:
16
0000000BF8EFFBE0
0000000BF8EFFBE0
0000000BF8EFFBE0
0000000BF8EFFBE0
Values before destruction: a: 15, x: 10, y: 13
The views can only see their single respective member variable (the others are protected). Casting from Combine to a base class (the 3 views) is allowed. There are no special requirements for the Complex class, not even standard-layout or default constructible.
The Complex class contains all the members and implementation, but the Combined class has to be constructed so that all the Views are static base classes.
In the example shown the views can only be created from inside the class with the view1/2/3() functions, as the inheritance is protected. One could do public inheritance, but then would have to explicitly make all members invisible to a view protected. And the chaining order of the views could be seen. But the advantage would be, that the views can be directly cast from the Combined class. This could perhaps also achieved with operator View1& conversion operators?
Destruction from a View pointer would be possible (not implemented here) as the views know the actual constructed (dynamic) class of the object (=Combined).
Those views only work for the class of an object known at compile-time, otherwise a conventional solution with virtual is necessary.
Is there an easier (legal) way for static (non-overhead) views, which are comfortable to use?
(One could always fall back to friend functions)