2017-10-12 6 views
0

저는 거의 동일하지만 아주 비슷하지 않은 한 쌍의 기본/파생 클래스가 있습니다.거의 동일한 base> 파생 된 클래스의 쌍에서 코드 중복을 피하십시오.

enter image description here

는 단순히 Base2 > Derived2을 만들 Base1 > Derived1의 모든 코드를 복사 할 수 있지만, 추한 것, 두 번 거의 모든 수정을 요구한다.

질문 : 코드 중복을 피하기 위해 두 쌍 사이에 가능한 한 많은 코드를 공유 할 수 있습니까?


실제 문제의 대부분의 기능을 가진 작은 장난감 예제를 만들려고했습니다. 나는 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의 인터페이스는 그 클래스 BInterface에서 상속하여 공유 할 수 있습니다.

추가 기본 클래스 DInterface을 통해 D1D2과 동일한 작업을 수행 할 수 있도록 다중 상속을 사용하는 것이 좋습니다. 또한, 흥미로운 반복 템플릿 패턴을 사용하여이 추가 기본 클래스가 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; 
} 

여러 사람이 내 실제 문제의 상황은 내가 실제 문제를 설명 아래, 손실됩니다 너무 광범위하고 논평 한 가입일 :

Mathematicaa C extension API 있습니다. 조밀하거나 희소 한 배열 또는 이미지와 같은 특정 데이터 유형은 C에서 조작 될 수 있습니다. C++ 인터페이스를 훨씬 쉽게 사용할 수 있도록 작업 중입니다. 이 시스템은 인터페이스 생성기에도 포함되어 있습니다. 많은 접착제 코드가 Mathematica의 C++ 클래스 인터페이스의 상징적 표현을 기반으로 자동 생성됩니다. Here's an older version of the system.

이제 이미지 처리 작업을하고 있습니다. Mathematica는 ImageImage3D을 가지고 있으며 2D와 3D 이미지에 대한 표현이 뚜렷합니다. Image은 바이트, 16 비트, 부동 소수점 등과 같은 다른 픽셀 유형을 가질 수도 있습니다.

C API는 2D 및 3D 이미지를 포함하여 이들 모두에 대해 단일 표현을 사용하며 MImage (포인터 유형, 복수 MImages는 메모리의 동일한 이미지를 가리킬 수 있습니다.

C++에서 2D 및 3D 이미지에 대해 별도의 클래스를 사용하고 픽셀 유형에 템플릿을 템플릿으로 만드는 것이 편리합니다. 위의 D1D2 클래스에 해당합니다. 그러나 일부 경우 픽셀 유형 (픽셀은이 경우 액세스 할 수 없지만 이미지로 다른 작업도 수행 할 수 있음)을 가질 수있는 "일반"이미지로 작동하는 것이 유용합니다.이것이 기본 클래스 B1B2입니다.

Here's the implementation of 2D image references (아직 완료되지 않았으며 변경 될 예정입니다.) 3D 이미지를 추가해야하는데 많은 코드가 공유됩니다.

+2

너무 광범위하다고 생각합니다. CRTP에 특별히 문제가 있으면 좋은 질문 일 수 있습니다. –

+0

또 다른 것은 DBase가 기본 클래스 (B1 또는 B2)에서 템플리트 화되는 템플리트 DBase의 관점에서 D1 및 D2를 작성하는 것입니다. (따라서 트리는'D1' ->'DBase '->'B1' ->'BInterface'처럼 보일 것입니다.). 또한 자유 함수 (템플릿 화 가능) 또는 전 처리기 매크로; 그것은 모두 당신의 실제 * 문제에 달려 있습니다. –

+0

@MartinBonner 왜 너무 광범위합니까? 문제는 개념에 관한 것이 아닙니다. 그것은 * 하나의 특별한 * 프로그램에 관한 것입니다. 나는 그것을 가능한 한 작게 증류했다. 문제는이 두 쌍의 클래스간에 코드를 공유하는 방법입니다. 이 접근법이이 특별한 경우에 충분히 합리적이라면, 그것은 내가 받아 들일 수있는 대답입니다. 나는 그것이 진실인지 아닌지 판단 할 수 없으며, 나에게 너무 복잡해 보입니다. – Szabolcs

답변

2

이 솔루션은 이름이 있고 이름을 가진 기본 클래스를 통해 값을 갖는 개념을 제외합니다.

파생 클래스의 개별 구성 요소가 서로 종속되지 않는 경우 이러한 종류의 상속 구성은 유지하기가 비교적 쉽습니다.

기본 클래스의 관심사가 상호 의존적 인 경우 파생 클래스를 통해 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; 
} 
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에서 name 개의 메소드를 액세스 가능하게 만들기 위해 using 공개를 사용합니다. (2) base2에서는 슈퍼 클래스 메소드를 호출하는 스텁 메서드를 작성하므로 이름 함수 중 하나만 필요합니다 (참고 :이 클래스는 using 메서드와 동일한 어셈블리 코드가되어야 함).

+0

이것은 하나만 제외하면 모든면에서 잘 작동하는 것처럼 보입니다.'base1'과'base2'가 모두'base1'과'base2'가 모두'base1 :: name()'을 상속받지 않고 Doxygen과 같은 도구로 문서화하는 방법을 모르겠습니다. 같은 클래스 (이것은 클래스 사용자로부터 숨겨져 있어야합니다) – Szabolcs

+0

나는 Doxygen을 도울 수 있는지 확신하지 못합니다. 개인 상속을 표시하지 않거나 적어도 숨길 수 있도록 구성 할 수 있습니다. 링크를 해제하려면 각 클래스의 함수를 별도로 문서화해야 할 수도 있습니다. 그러나 공통 기본 클래스를 사용한다는 것을 알고있는 사용자는 해당 정보를 사용할 수 없어야하는 등 나쁜 점이 아닙니다. –