C ++ 17의 변형에 포함 된 유형에 대한 무의미한 메서드를 처리하는 방법

세바스찬 K.

특정 컨테이너 클래스가 사용자 지정 클래스의 변형을 보유해야하는 경우를 다루고 있습니다 (특히 벡터에서 그러한 인스턴스를 수집하기 위해). 차례로 이것들은 서로 상호 연관되어 있습니다. 코드 예제에서이 변형의 유형은 BirdFish이고 컨테이너 클래스는입니다 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_인스턴스로 aAnimalContainer사용 클래스 a.WingSpan(),이 동물은이어야 Bird뿐만 아니라 depth_of_dive_사용하는 것은 a.DepthOfDive(), 그것이되어야 Fish. 또한, 각 BirdFish하는 (생리 학적 비현실적인) 무게 즉, 추정 할 수 a.EstimatedWeight()호출 할 수 있습니다.

기본적으로 컴파일러 오류를 피하기 위해 메서드 WingSpan()가 Fish 클래스에 DepthOfDive()추가되고 Bird 클래스에 추가됩니다.

이러한 더미 메서드 추가는 특히 두 개 이상의 변형 (여기 FishBird)이 관련되거나 이러한 클래스에 많은 메서드가 포함 된 경우 매우 번거로울 수 있습니다 .

한 가지 가능성은 특정 클래스에 대해 방문자를 오버로드하고 다른 모든 경우 (다시 일반 람다 사용)에서 경고를 반환하는 것처럼 보이지만 이로 인해 프로세스가 약간 향상되지만 매우 번거 롭습니다 (아래 두 번째 코드 예제 참조). .

복사 및 붙여 넣기가 덜 필요한보다 포괄적 인 방법으로이를 처리하는 방법에 대한 제안이 있습니까? 이 개념에 일반적인 문제가있는 경우 조언도 환영합니다.

그건 그렇고, 동물 컨테이너 클래스는 나중에 더미 함수의 의도하지 않은 호출을 피하기 위해 사용자를 안내하는 다른 클래스에 배치됩니다.

첫 번째 작업 코드 예제

#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] 삭제

에서 수정
0

몇 마디 만하겠습니다

0리뷰
로그인참여 후 검토

관련 기사

분류에서Dev

동일한 명명 된 메서드에 대한 Typescript 유형 정의를 만드는 방법

분류에서Dev

다른 유형의 유사한 함수에 대해 C #에서 중복 코드를 줄이는 방법

분류에서Dev

인터페이스 메서드에서 반환 된 값에 대해 동일한 유형의 클래스를 지정하는 방법은 무엇입니까?

분류에서Dev

인터페이스 메서드에서 반환 된 값에 대해 동일한 유형의 클래스를 지정하는 방법은 무엇입니까?

분류에서Dev

지정된 유형의 중첩 된 객체를 포함하는 문서에 대해 RavenDB를 쿼리하는 방법

분류에서Dev

방법 : LINQ를 사용하여 가능한 여러 유형의 "작은 따옴표"가 포함 된 문자열에 대한 데이터 검색

분류에서Dev

C ++의 소멸자 함수에서 병합 된 LL에 대한 메모리를 올바르게 할당 해제하는 방법은 무엇입니까?

분류에서Dev

함수 C ++에 대한 포인터의 스레드를 만드는 방법

분류에서Dev

식별 된 공용체 유형의 배열에 대한 typeguard를 작성하는 방법은 무엇입니까?

분류에서Dev

메서드 링크 된 제네릭 유형에 대한 TRTTIType을 얻는 방법

분류에서Dev

C #의 형식 안전 컬렉션에 제한된 제네릭 형식이있는 작업 대리자를 저장하는 방법은 무엇입니까?

분류에서Dev

포함 된 슬라이스로 크기가 지정되지 않은 유형에 대한 스마트 포인터를 만드는 방법은 무엇입니까?

분류에서Dev

TypeScript에서 미리 정의 된 허용 가능한 값으로 사용자 정의 유형을 올바르게 선언하는 방법은 무엇입니까?

분류에서Dev

함수 반환 유형에 대해 역 참조 된 유형의 템플릿 멤버를 얻는 방법

분류에서Dev

부트 스트랩, LARGE (lg) 정의에 포함 된 다양한 해상도를 처리하는 방법은 무엇입니까?

분류에서Dev

함수에 대한 컨텍스트 유형 정의를 바꾸는 방법

분류에서Dev

C ++의 std :: function에 포함 된 람다 (캡처 중일 수 있음)를 고유하게 식별하는 방법

분류에서Dev

SOLR에서 "문서에 적어도 하나의 거대한 용어가 포함되어 있습니다"를 처리하는 방법은 무엇입니까?

분류에서Dev

별도의 스레드에서 실행되는 함수에 대한 이동 된 포인터를 검색하는 방법은 무엇입니까?

분류에서Dev

"DEFINED"상수에 대한 포인터를 C의 함수에 전달하는 방법은 무엇입니까?

분류에서Dev

C ++ typedef 유형에 대한 생성자를 만드는 방법

분류에서Dev

Objective-C initXXX 메서드를 동일한 유형의 인수가있는 Xamarin.iOS의 생성자에 바인딩하는 방법은 무엇입니까?

분류에서Dev

템플릿 멤버 함수의 특수 변형에 대한 포인터를 만드는 방법은 무엇입니까?

분류에서Dev

메모리에 대한 C에서 열거 형의 데이터 유형 정의

분류에서Dev

MultiUrlPicker를 포함하는 Umbraco에 대한 사용자 정의 데이터 유형

분류에서Dev

TypeScript의 함수에 대한 반환 유형에서 매개 변수 값을 속성 이름으로 사용하는 방법은 무엇입니까?

분류에서Dev

C ++의 switch 문에서 다양한 유형의 변수를 반환하는 방법

분류에서Dev

ctypes를 사용하여 유형 포인터의 매개 변수를 C 함수에 대한 포인터로 전달

분류에서Dev

누락 된 변수 조합에 대한 NA를 R의 긴 형식 data.frame에 추가하는 방법

Related 관련 기사

  1. 1

    동일한 명명 된 메서드에 대한 Typescript 유형 정의를 만드는 방법

  2. 2

    다른 유형의 유사한 함수에 대해 C #에서 중복 코드를 줄이는 방법

  3. 3

    인터페이스 메서드에서 반환 된 값에 대해 동일한 유형의 클래스를 지정하는 방법은 무엇입니까?

  4. 4

    인터페이스 메서드에서 반환 된 값에 대해 동일한 유형의 클래스를 지정하는 방법은 무엇입니까?

  5. 5

    지정된 유형의 중첩 된 객체를 포함하는 문서에 대해 RavenDB를 쿼리하는 방법

  6. 6

    방법 : LINQ를 사용하여 가능한 여러 유형의 "작은 따옴표"가 포함 된 문자열에 대한 데이터 검색

  7. 7

    C ++의 소멸자 함수에서 병합 된 LL에 대한 메모리를 올바르게 할당 해제하는 방법은 무엇입니까?

  8. 8

    함수 C ++에 대한 포인터의 스레드를 만드는 방법

  9. 9

    식별 된 공용체 유형의 배열에 대한 typeguard를 작성하는 방법은 무엇입니까?

  10. 10

    메서드 링크 된 제네릭 유형에 대한 TRTTIType을 얻는 방법

  11. 11

    C #의 형식 안전 컬렉션에 제한된 제네릭 형식이있는 작업 대리자를 저장하는 방법은 무엇입니까?

  12. 12

    포함 된 슬라이스로 크기가 지정되지 않은 유형에 대한 스마트 포인터를 만드는 방법은 무엇입니까?

  13. 13

    TypeScript에서 미리 정의 된 허용 가능한 값으로 사용자 정의 유형을 올바르게 선언하는 방법은 무엇입니까?

  14. 14

    함수 반환 유형에 대해 역 참조 된 유형의 템플릿 멤버를 얻는 방법

  15. 15

    부트 스트랩, LARGE (lg) 정의에 포함 된 다양한 해상도를 처리하는 방법은 무엇입니까?

  16. 16

    함수에 대한 컨텍스트 유형 정의를 바꾸는 방법

  17. 17

    C ++의 std :: function에 포함 된 람다 (캡처 중일 수 있음)를 고유하게 식별하는 방법

  18. 18

    SOLR에서 "문서에 적어도 하나의 거대한 용어가 포함되어 있습니다"를 처리하는 방법은 무엇입니까?

  19. 19

    별도의 스레드에서 실행되는 함수에 대한 이동 된 포인터를 검색하는 방법은 무엇입니까?

  20. 20

    "DEFINED"상수에 대한 포인터를 C의 함수에 전달하는 방법은 무엇입니까?

  21. 21

    C ++ typedef 유형에 대한 생성자를 만드는 방법

  22. 22

    Objective-C initXXX 메서드를 동일한 유형의 인수가있는 Xamarin.iOS의 생성자에 바인딩하는 방법은 무엇입니까?

  23. 23

    템플릿 멤버 함수의 특수 변형에 대한 포인터를 만드는 방법은 무엇입니까?

  24. 24

    메모리에 대한 C에서 열거 형의 데이터 유형 정의

  25. 25

    MultiUrlPicker를 포함하는 Umbraco에 대한 사용자 정의 데이터 유형

  26. 26

    TypeScript의 함수에 대한 반환 유형에서 매개 변수 값을 속성 이름으로 사용하는 방법은 무엇입니까?

  27. 27

    C ++의 switch 문에서 다양한 유형의 변수를 반환하는 방법

  28. 28

    ctypes를 사용하여 유형 포인터의 매개 변수를 C 함수에 대한 포인터로 전달

  29. 29

    누락 된 변수 조합에 대한 NA를 R의 긴 형식 data.frame에 추가하는 방법

뜨겁다태그

보관