避免在一对几乎相同的 base > derived 类 中重复代码
Avoid code duplication in a pair of almost identical base > derived classes
我有一对 base/derived class 几乎相同,但不完全相同。
我可以简单地复制 Base1 > Derived1
的所有代码来创建 Base2 > Derived2
,但这会很丑陋,并且几乎需要进行两次修改。
问题:如何在两对之间共享尽可能多的代码,以避免代码重复?
我试图创建一个具有实际问题的大部分特征的小玩具示例。我想避免 D1
和 D2
接口的相同部分有重复代码。如果您想查看更多 实际 问题,请滚动到问题末尾。
#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;
}
B1
和 B2
的接口可以通过继承 class BInterface
.
来共享
有人建议我使用多重继承,以便能够通过额外的基础 class DInterface
对 D1
和 D2
执行相同的操作。此外,有人建议我尝试使用奇怪的重复模板模式来允许这个额外的基础 class 访问 D1
和 D2
的成员。我尝试这样做如下。我觉得有点复杂,我想知道这种做法是否合理,是否有更好的方法。
#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 很方便,也可以在像素类型上对它们进行模板化。这些对应于上面的 D1
和 D2
classes。但是,在某些情况下,对可能具有任何像素类型的 "generic" 图像进行操作很有用(在这种情况下无法访问像素,但我们也可以对图像执行其他操作)。这就是为什么我还有基础 classes B1
和 B2
.
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
方法)。
我有一对 base/derived class 几乎相同,但不完全相同。
我可以简单地复制 Base1 > Derived1
的所有代码来创建 Base2 > Derived2
,但这会很丑陋,并且几乎需要进行两次修改。
问题:如何在两对之间共享尽可能多的代码,以避免代码重复?
我试图创建一个具有实际问题的大部分特征的小玩具示例。我想避免 D1
和 D2
接口的相同部分有重复代码。如果您想查看更多 实际 问题,请滚动到问题末尾。
#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;
}
B1
和 B2
的接口可以通过继承 class BInterface
.
有人建议我使用多重继承,以便能够通过额外的基础 class DInterface
对 D1
和 D2
执行相同的操作。此外,有人建议我尝试使用奇怪的重复模板模式来允许这个额外的基础 class 访问 D1
和 D2
的成员。我尝试这样做如下。我觉得有点复杂,我想知道这种做法是否合理,是否有更好的方法。
#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 很方便,也可以在像素类型上对它们进行模板化。这些对应于上面的 D1
和 D2
classes。但是,在某些情况下,对可能具有任何像素类型的 "generic" 图像进行操作很有用(在这种情况下无法访问像素,但我们也可以对图像执行其他操作)。这就是为什么我还有基础 classes B1
和 B2
.
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
方法)。