避免在一对几乎相同的 base > derived 类 中重复代码

Avoid code duplication in a pair of almost identical base > derived classes

我有一对 base/derived class 几乎相同,但不完全相同。

我可以简单地复制 Base1 > Derived1 的所有代码来创建 Base2 > Derived2,但这会很丑陋,并且几乎需要进行两次修改。

问题:如何在两对之间共享尽可能多的代码,以避免代码重复?


我试图创建一个具有实际问题的大部分特征的小玩具示例。我想避免 D1D2 接口的相同部分有重复代码。如果您想查看更多 实际 问题,请滚动到问题末尾。

#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;
}

B1B2 的接口可以通过继承 class BInterface.

来共享

有人建议我使用多重继承,以便能够通过额外的基础 class DInterfaceD1D2 执行相同的操作。此外,有人建议我尝试使用奇怪的重复模板模式来允许这个额外的基础 class 访问 D1D2 的成员。我尝试这样做如下。我觉得有点复杂,我想知道这种做法是否合理,是否有更好的方法。

#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;
}

由于几个人评论说这太笼统了,而且我的实际问题的上下文已经丢失,下面我将描述实际问题:

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.

我现在正在处理图像。 Mathematica 具有 Image and Image3D,用于 2D 和 3D 图像的不同表达式。 Image也可以有不同的像素类型,如字节、16位、浮点等

C API对所有这些使用单一表示,包括2D和3D图像,称为MImage(一个指针类型,多个MImages可能指向同一个图像在内存中)。

在 C++ 中为 2D 和 3D 图像设置单独的 class 很方便,也可以在像素类型上对它们进行模板化。这些对应于上面的 D1D2 classes。但是,在某些情况下,对可能具有任何像素类型的 "generic" 图像进行操作很有用(在这种情况下无法访问像素,但我们也可以对图像执行其他操作)。这就是为什么我还有基础 classes B1B2.

Here's the implementation of 2D image references 到目前为止(这还没有完成,它会改变)。还需要添加3D图片,会分享很多代码。

此解决方案通过具有名称的基 class 分解出具有名称和具有值的概念。

如果派生的classes的各个组件之间不相互依赖,那么这种继承组合相对容易维护。

如果基础 classes 的关注点是相互依赖的,那么您将不得不通过派生的 class.

使用 CRTP 和编组调用
#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;
} 

如果您想继承代码重用,可以使用私有继承。通过私有继承,派生的 classes 被阻止转换为它们的基础 classes.

#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;
}

使用私有继承,您无法直接访问派生 class 之外的任何基 class 方法。您有两种选择来实现这一点:(1) 在 base1 中,我们使用 public using 语句使两个 name 方法可访问。 (2) 在 base2 中,我们只需要其中一个名称函数,因此我们编写了一个调用 super class 方法的存根方法(注意:因为这是内联的,它应该产生与using 方法)。