특정 컨테이너 클래스가 사용자 지정 클래스의 변형을 보유해야하는 경우를 다루고 있습니다 (특히 벡터에서 그러한 인스턴스를 수집하기 위해). 차례로 이것들은 서로 상호 연관되어 있습니다. 코드 예제에서이 변형의 유형은 Bird
및 Fish
이고 컨테이너 클래스는입니다 AnimalContainer
(완전한 작동 코드는 아래 참조).
불완전한 수업 개요 :
using namespace std;
using uint = unsigned int;
class Animal {
protected:
uint length_;
};
class Fish : public Animal {
private:
uint depths_of_dive_;
};
class Bird : public Animal {
private:
uint wing_span_;
};
class AnimalContainer {
private:
variant<Bird, Fish> the_animal_;
};
이제 (펭귄과 다른 새들을 무시하고) , 새들은 보통 잠수 할 수없고, 물고기는 날개 가 없습니다 (적어도 들어 본 적 없음) . 그러나 코드를 요청할 가능성을 제공한다 wing_span_
인스턴스로 a
의 AnimalContainer
사용 클래스 a.WingSpan()
,이 동물은이어야 Bird
뿐만 아니라 depth_of_dive_
사용하는 것은 a.DepthOfDive()
, 그것이되어야 Fish
. 또한, 각 Bird
과 Fish
하는 (생리 학적 비현실적인) 무게 즉, 추정 할 수 a.EstimatedWeight()
호출 할 수 있습니다.
기본적으로 컴파일러 오류를 피하기 위해 메서드 WingSpan()
가 Fish 클래스에 DepthOfDive()
추가되고 Bird 클래스에 추가됩니다.
이러한 더미 메서드 추가는 특히 두 개 이상의 변형 (여기 Fish
및 Bird
)이 관련되거나 이러한 클래스에 많은 메서드가 포함 된 경우 매우 번거로울 수 있습니다 .
한 가지 가능성은 특정 클래스에 대해 방문자를 오버로드하고 다른 모든 경우 (다시 일반 람다 사용)에서 경고를 반환하는 것처럼 보이지만 이로 인해 프로세스가 약간 향상되지만 매우 번거 롭습니다 (아래 두 번째 코드 예제 참조). .
복사 및 붙여 넣기가 덜 필요한보다 포괄적 인 방법으로이를 처리하는 방법에 대한 제안이 있습니까? 이 개념에 일반적인 문제가있는 경우 조언도 환영합니다.
그건 그렇고, 동물 컨테이너 클래스는 나중에 더미 함수의 의도하지 않은 호출을 피하기 위해 사용자를 안내하는 다른 클래스에 배치됩니다.
첫 번째 작업 코드 예제
#include <variant>
#include <iostream>
using namespace std;
using uint = unsigned int;
class Animal {
public:
Animal(uint length) : length_{length} {}
uint Length() { return length_; }
protected:
uint length_;
};
class Fish : public Animal {
public:
Fish(uint length, uint depths_of_dive) : Animal(length), depths_of_dive_{depths_of_dive} {}
uint DepthOfDive() { return depths_of_dive_; }
uint EstimatedWeight() { return length_ * length_; }
uint WingSpan() { cerr << "Usually fishes do not have wings... "; return 0; }
private:
uint depths_of_dive_;
};
class Bird : public Animal {
public:
Bird(uint length, uint wing_span) : Animal(length), wing_span_{wing_span} {}
uint WingSpan() { return wing_span_; }
uint EstimatedWeight() { return wing_span_ * length_; }
uint DepthOfDive() { cerr << "Usually birds can not dive... "; return 0; }
private:
uint wing_span_;
};
class AnimalContainer {
public:
AnimalContainer(Bird b) : the_animal_{b} {}
AnimalContainer(Fish f) : the_animal_{f} {}
uint Length() {
return visit([] (auto arg) { return arg.Length(); }, the_animal_);
}
uint WingSpan() {
return visit([] (auto arg) { return arg.WingSpan(); }, the_animal_);
}
uint DepthOfDive() {
return visit([] (auto arg) { return arg.DepthOfDive(); }, the_animal_);
}
uint EstimatedWeight() {
return visit([] (auto arg) { return arg.EstimatedWeight(); }, the_animal_);
}
private:
variant<Bird, Fish> the_animal_;
};
int main()
{
Fish f(2,3);
Bird b(2,3);
AnimalContainer a_1(f);
AnimalContainer a_2(b);
cout << a_1.Length() << ' ' << a_1.WingSpan() << ' ' << a_1.DepthOfDive() << ' ' << a_1.EstimatedWeight() << endl;
cout << a_2.Length() << ' ' << a_2.WingSpan() << ' ' << a_2.DepthOfDive() << ' ' << a_2.EstimatedWeight() << endl;
return 0;
}
두 번째 작업 코드 예제
#include <variant>
#include <iostream>
using namespace std;
using uint = unsigned int;
class Animal {
public:
Animal(uint length) : length_{length} {}
uint Length() { return length_; }
protected:
uint length_;
};
class Fish : public Animal {
public:
Fish(uint length, uint depths_of_dive) : Animal(length), depths_of_dive_{depths_of_dive} {}
uint DepthOfDive() { return depths_of_dive_; }
uint EstimatedWeight() { return length_ * length_; }
// no more dummy function
private:
uint depths_of_dive_;
};
class Bird : public Animal {
public:
Bird(uint length, uint wing_span) : Animal(length), wing_span_{wing_span} {}
uint WingSpan() { return wing_span_; }
uint EstimatedWeight() { return wing_span_ * length_; }
// no more dummy function
private:
uint wing_span_;
};
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
class AnimalContainer {
public:
AnimalContainer(Bird b) : the_animal_{b} {}
AnimalContainer(Fish f) : the_animal_{f} {}
uint Length() {
return visit([] (auto arg) { return arg.Length(); }, the_animal_);
}
uint WingSpan() {
return visit(overloaded { // now overloaded version
[] (auto) { cerr << "This animal does not have wings... "; return uint(0); },
[] (Bird arg) { return arg.WingSpan(); }}, the_animal_);
}
uint DepthOfDive() {
return visit(overloaded { // now overloaded version
[] (auto) { cerr << "This animal can not dive... "; return uint(0); },
[] (Fish arg) { return arg.DepthOfDive(); }}, the_animal_);
}
uint EstimatedWeight() {
return visit([] (auto arg) { return arg.EstimatedWeight(); }, the_animal_);
}
private:
variant<Bird, Fish> the_animal_;
};
int main()
{
Fish f(2,3);
Bird b(2,3);
AnimalContainer a_1(f);
AnimalContainer a_2(b);
cout << a_1.Length() << ' ' << a_1.WingSpan() << ' ' << a_1.DepthOfDive() << ' ' << a_1.EstimatedWeight() << endl;
cout << a_2.Length() << ' ' << a_2.WingSpan() << ' ' << a_2.DepthOfDive() << ' ' << a_2.EstimatedWeight() << endl;
return 0;
}
먼저 새로운 기여자로부터 디자인에 대해 잘 정리 된 질문을 보게되어 매우 기쁩니다. StackOverflow에 오신 것을 환영합니다! :)
올바르게 언급했듯이 콘크리트 클래스 또는 컨테이너에서 존재하지 않는 동작을 처리하는 두 가지 선택 사항이 있습니다. 두 가지 옵션을 모두 고려해 보겠습니다.
이것은 일반적으로 상속 및 (동적) 다형성, 고전적인 OOP 접근 방식의 도움으로 수행됩니다. 관련없는 클래스에 사용 variant
되기 때문에이 경우 에는 없어야합니다 variant
. 이미 공통 기본 클래스가있을 때 사용하는 것은 의미가 없습니다.
대신 기본 클래스에서 필요한 전체 인터페이스를 가상 함수 집합으로 정의하십시오. 좋은 방법은 계층 구조의 맨 위에 순수한 인터페이스를 갖는 것입니다. 그런 다음 선택적으로 일부 기본 구현을 제공하는 중간 (추상적 일 수 있음) 클래스를 가질 수 있습니다. 이렇게하면 각각의 새로운 파생 동물에 대한 관련없는 개념에 대해 생각하지 않고 일부 코드 중복을 피할 수 있습니다.
코드는 다음과 같을 수 있습니다 (테스트되지 않고 개념 만 보여줌).
// Pure interface on top of the hierarchy
class IAnimal {
public:
virtual ~IAnimal() = default.
virtual uint Length() const = 0;
virtual uint DepthOfDive() const = 0;
virtual uint EstimatedWeight() const = 0;
virtual uint WingSpan() const = 0;
};
// Intermediate class with some common implementations
class Animal : public IAnimal {
public:
Animal(uint length) : length_{length} {}
// We know how to implement this on this level already, so mark this final
// Otherwise it won't have much sense to have the length_ field
uint Length() const final { return length_; }
// Some of these should be overridden by the descendants
uint DepthOfDive() const override { cerr << "This creature can not dive... "; return 0; }
uint WingSpan() const override { cerr << "This creature does not have wings... "; return 0; }
private:
uint length_; // Better make it private
};
class Fish : public Animal {
public:
Fish(uint length, uint depths_of_dive) : Animal(length), depths_of_dive_{depths_of_dive} {}
uint DepthOfDive() const { return depths_of_dive_; }
uint EstimatedWeight() const { return Length() * Length(); }
private:
uint depths_of_dive_;
};
class Bird : public Animal {
public:
Bird(uint length, uint wing_span) : Animal(length), wing_span_{wing_span} {}
uint WingSpan() const { return wing_span_; }
uint EstimatedWeight() const { return wing_span_ * Length(); }
private:
uint wing_span_;
};
using AnimalContainer = std::unique_ptr<IAnimal>;
이제 통합 컨테이너 대신 기본 인터페이스에 대한 포인터를 직접 사용할 수 있습니다. 권위 있는.
몇 가지 공통 인터페이스를 제공하는 통합 컨테이너를 갖는 것은 기본 클래스가 없을 때 의미가있을 수 있습니다. 그렇지 않으면 위에서 설명한 고전적인 OOP로 돌아가는 것이 좋습니다. 따라서이 경우 Animal
클래스를 완전히 제거 하고 모든 특정 동물에 필요한 방식으로 필요한 것을 정의하는 것이 좋습니다.
구현과 관련하여 귀하의 접근 방식은 실제로 멋진 overloaded
패턴을 사용하는 것이 좋습니다 . 내가 고려할 수있는 유일한 방법은 if constexpr
내부 가 많은 방문자로 단일 일반 람다를 사용하는 것 입니다. 일부 상황에서는 읽기가 더 쉬울 수 있습니다. 그러나 이것은 정말로 달려 있으며 접근 방식에 나쁜 것이 없습니다.
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다