I have a pair of base/derived classes which are almost identical, but not quite.
I could simply copy all the code of Base1 > Derived1
to create Base2 > Derived2
, but that would be ugly, and would require making almost any modification twice.
Question: How can I share as much code as possible between the two pairs, to avoid code duplication?
I tried to create a small toy example that has most features of the actual problem. I want to avoid having duplicate code for the identical part of the interface of D1
and D2
. If you want to see more of the actual problem, scroll to the end of the question.
#include <iostream>
using namespace std;
//////////// 1st PAIR ////////////
class B1 {
protected:
string name;
public:
B1() : name("B1") { } // constructors are different between B1 and B2
void speak() { cout << name << endl; } // identical between B1 and B2
};
template<typename T>
class D1 : public B1 {
T x; // identical between D1 and D2
public:
D1(const T &a) { x = a + name.size(); } // refers to base class member
int getX() { return x; } // identical between D1 and D2
int nameLength() { return name.size(); } // accesses member of B, identical between D1 and D2
// differences between D1 and D2 follow:
int add(int i, int j) { return i+j; } // different signature between D1 and D2
void more() {} // not present in D1
};
//////////// 2nd PAIR ////////////
class B2 {
protected:
string name;
public:
B2() : name("B2") { }
void speak() { cout << name << endl; }
};
template<typename T>
class D2 : public B2 {
T x; // identical between D1 and D2
public:
D2(const T &a) { x = a + name.size(); }
int getX() { return x; } // identical between D1 and D2
int nameLength() { return name.size(); } // accesses member of B, identical between D1 and D2
int add(int i, int j, int k) { return i+j+k; } // different signature between D1 and D2
};
// this is just to test that the program compiles and works
int main() {
D1<int> d1(5);
D2<long> d2(6l);
cout << d1.getX();
cout << d1.nameLength();
return 0;
}
The interface of B1
and B2
can be shared by making them inherit from a class BInterface
.
It was suggested to me to use multiple inheritance to be able to do the same with D1
and D2
, through an additional base class DInterface
. Furthermore, it was suggested I try to use the curiously recurring template pattern to allow this additional base class to access the members of D1
and D2
. My attempt at doing this follows. I find it a bit complicated, and I would like to know if this is a reasonable approach, and whether there is a better way to do the same.
#include <iostream>
using namespace std;
//////////// COMMON INTERFACES ////////////
class BInterface {
protected:
string name;
BInterface(const string &n) : name(n) { }
public:
void speak() { cout << name << endl; }
};
template<typename D>
class DInterface {
private:
D &derived() { return *static_cast<D *>(this); }
protected:
DInterface() {}
public:
int getX() { return derived().x; }
int nameLength() { return derived().name.size(); }
};
//////////// 1st PAIR ////////////
class B1 : public BInterface {
public:
B1() : BInterface("B1") { } // constructors are different between B1 and B2
};
template<typename T>
class D1 : public B1, public DInterface< D1<T> > {
friend class DInterface< D1<T> >;
T x; // identical between D1 and D2
public:
D1(const T &a) { x = a + name.size(); } // refers to base class member
int add(int i, int j) { return i+j; } // different signature between D1 and D2
void more() {} // not present in D1
};
//////////// 2nd PAIR ////////////
class B2 : public BInterface {
public:
B2() : BInterface("B2") { }
};
template<typename T>
class D2 : public B2, public DInterface< D2<T> > {
friend class DInterface< D2<T> >;
T x; // identical between D1 and D2
public:
D2(const T &a) { x = a + name.size(); }
int add(int i, int j, int k) { return i+j+k; } // different signature between D1 and D2
};
// this is just to test that the program compiles and works
int main() {
D1<int> d1(5);
D2<long> d2(6l);
cout << d1.getX();
cout << d1.nameLength();
return 0;
}
Since several people have commented that this is too broad, and that the context from my actual problem is lost, below I will describe the actual problem:
Mathematica has a C extension API. Certain data types, such as dense or sparse arrays or images can be manipulated in C. I am working on a much easier to use C++ interface. The system also includes in interface generator: a lot of glue code is automatically generated based on a symbolic representation of a C++ class interface in Mathematica. Here's an older version of the system.
I am now working on handling images. Mathematica has Image
and Image3D
, distinct expressions for 2D and 3D images. Image
can also have different pixel types, such as byte, 16-bit, floating point, etc.
The C API uses a single representation for all of these, including 2D and 3D images, called MImage
(a pointer type, multiple MImages
may point to the same image in memory).
It is convenient for have separate classes for 2D and 3D images in C++, and also to template these on the pixel type. These correspond to the D1
and D2
classes above. However, in some cases, it is useful to operate with "generic" images that may have any pixel type (pixels can't be accessed in this case, but we can do other things with the images too). This is why I also have the base classes B1
and B2
.
Here's the implementation of 2D image references so far (this is not done and it will change). I still need to add 3D images, which will share a lot of code.
答案 0 :(得分:2)
此解决方案通过具有名称的基类来解释拥有名称和具有值的概念。
如果派生类的各个组件不相互依赖,那么这种继承组合相对容易维护。
如果基类的关注点是相互依赖的,那么你将不得不通过派生类使用CRTP和marshal调用。
#include <iostream>
using namespace std;
// factor out common parts
struct NamedThing
{
NamedThing(std::string &&name) : name(std::move(name)) {}
NamedThing(std::string const& name) : name(name) {}
void speak() { cout << name << endl; }
std::size_t nameLength() const { return name.size(); }
private:
std::string name;
};
template<class T, class Base>
struct NamedValue : public Base
{
T x; // identical between D1 and D2
public:
NamedValue(T const& v)
: Base()
, x(this->nameLength())
{}
T getX() { return x; } // identical between D1 and D2
};
//////////// 1st PAIR ////////////
class B1 : public NamedThing
{
public:
B1() : NamedThing("B1") { } // constructors are different between B1 and B2
};
template<typename T>
class D1 : public NamedValue<T, B1> {
using inherited = NamedValue<T, B1>;
public:
D1(const T &a)
: inherited(a)
{
}
// differences between D1 and D2 follow:
int add(int i, int j) { return i+j; } // different signature between D1 and D2
void more() {} // not present in D1
};
//////////// 2nd PAIR ////////////
class B2 : public NamedThing
{
public:
B2() : NamedThing("B2") { }
};
template<typename T>
class D2 : public NamedValue<T, B2> {
using inherited = NamedValue<T, B2>;
public:
D2(const T &a)
: inherited(a)
{
}
int add(int i, int j, int k) { return i+j+k; } // different signature between D1 and D2
};
// this is just to test that the program compiles and works
int main() {
D1<int> d1(5);
D2<long> d2(6l);
cout << d1.getX();
cout << d1.nameLength();
return 0;
}
答案 1 :(得分:1)
如果要继承代码重用,可以使用私有继承。使用私有继承,可以阻止派生类转换为其基类。
#include <string>
#include <iostream>
class super
{
std::string name_;
public:
super( std::string n ): name_(n) {}
virtual ~super(){}
std::string name() const { return this->name_; }
void name( std::string n ) { this->name_ = n; }
};
class base1: private super
{
int vertices_;
public:
base1( std::string n, int v ): super( n ), vertices_( v ) {}
virtual ~base1() {}
using super::name; // make both name methods accessible
int vertices() const { return this->vertices_; }
void vertices( int v ) { this->vertices_ = v; }
};
class base2: private super
{
std::string surname_;
public:
base2( std::string n, std::string s ): super( n ), surname_( s ) {}
virtual ~base2() {}
// to make only one name method accessible
std::string name() const { return this->super::name(); }
std::string surname() const { return this->surname_; }
};
// class derived1: public base1 { ... };
// class derived2: public base2 { ... };
int main()
{
base1 v1( "triangle", 3 );
base2 v2( "john", "doe" );
std::cout << "base1: " << v1.name() << " " << v1.vertices() << "\n";
std::cout << "base2: " << v2.name() << " " << v2.surname() << "\n";
v1.name( "square" );
v1.vertices( 4 );
std::cout << "base1: " << v1.name() << " " << v1.vertices() << "\n";
//v2.name( "jane" ); // illegal code
//super *p1 = &v1; // illegal code
//super *p2 = &v2; // illegal code
//derived1 d1(...);
//derived2 d2(...);
//base1 *p1 = &d1; // allowed
//base2 *p2 = &d2; // allowed
//derived1 *p1 = dynamic_cast< derived1* >((super*)&d2); // Not allowed
return 0;
}
使用私有继承,您无法直接访问派生类之外的任何基类方法。您有两种方法可以允许这样做:(1)在base1
中,我们使用公共using
语句来使两个name
方法可访问。 (2)在base2
中我们只需要一个名称函数,所以我们编写一个调用超类方法的存根方法(注意:因为这是内联的,它应该导致与{{1}相同的汇编代码方法)。