C ++에서 오버로드 된 생성자를 통해 알 수없는 유형의 변수 초기화


22

주로 파이썬 배경에서 나오기 때문에 C ++에서 유형 작업에 다소 어려움을 겪었습니다.

다른 유형을 매개 변수로 사용하는 여러 오버로드 된 생성자 중 하나를 통해 클래스 변수를 초기화하려고합니다. auto키워드 를 사용하여 변수의 자동 선언에 사용할 수 있지만 내 경우에는 생성자를 선택할 때까지 초기화되지 않습니다. 그러나 컴파일러는 초기화하지 않는 것에 만족하지 않습니다 value.

class Token {
public:

    auto value;

    Token(int ivalue) {
        value = ivalue;
    }
    Token(float fvalue) {
        value = fvalue;
    }
    Token(std::string svalue) {
        value = svalue;
    }

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

파이썬에서는 다음과 같이 보일 수 있습니다.

class Token():
        def __init__(self, value):
             self.value = value

        def printValue(self):
             print("The token value is: %s" % self.value)

auto이 시나리오 에서 키워드 를 사용하는 올바른 방법은 무엇입니까 ? 다른 접근법을 함께 사용해야합니까?


2
auto클래스 멤버에게는 전혀 사용할 수 없다고 생각 합니까? 관련이 있지만 오래된 질문 : "자동"멤버 변수를 가질 수 있습니까?
Yksisarvinen

템플릿을 사용하지 않는 이유는 무엇입니까?
Jimmy RT

파이썬을 사용하면 런타임에 각 작업에서 유형이 결정됩니다. 오버 헤드가 필요하지만 변수 유형을 한 명령문에서 다음 명령문으로 변경할 수 있습니다. C ++에서 형식을 미리 알아야 코드가 컴파일 될 수 있습니다. float 및 int는 서로 다른 이진 레이아웃을 가지며 다른 어셈블리 지침을 사용해야합니다. 런타임시 유연성을 원한다면 각 유형에 대해 유효한 코드가 포함 된 많은 분기 중 하나를 선택하는 변형과 같은 공용체 유형을 사용해야하며 성능 오버 헤드가 추가됩니다. int와 float 버전을 별도로 유지하려면 템플릿을 사용하십시오.
Jake

답변:


17

C ++에서 오버로드 된 생성자를 통해 알 수없는 유형의 변수 초기화

C ++에는 "알 수없는 유형의 변수"와 같은 것은 없습니다.

이 시나리오에서 자동 키워드를 사용하는 올바른 방법은 무엇입니까?

자동 삭감 된 변수에는 초 기자에서 추론되는 유형이 있습니다. 이니셜 라이저가 없으면 auto를 사용할 수 없습니다. 비 정적 멤버 변수에는 auto를 사용할 수 없습니다. 클래스의 한 인스턴스는 다른 인스턴스와 다른 유형의 멤버를 가질 수 없습니다.

이 시나리오에서는 자동 키워드를 사용하는 방법이 없습니다.

다른 접근법을 함께 사용해야합니까?

아마. 구현하려고하는 것 같습니다 std::variant. X 숫자 유형 중 하나를 저장하는 변수가 필요한 경우 사용해야합니다.

그러나 C ++에서 동적 입력을 모방하려고 할 수 있습니다. 파이썬에 대한 경험으로 인해 친숙 할 수도 있지만 대부분의 경우 이상적인 접근 방식은 아닙니다. 예를 들어,이 특정 예제 프로그램에서는 멤버 변수로 수행하는 모든 것이 인쇄됩니다. 따라서 각 경우에 문자열을 저장하는 것이 더 간단합니다. 다른 접근법은 Fire Lancer가 보여주는 Rhathin 또는 OOP 스타일의 동적 다형성 으로 표시되는 정적 다형성 입니다.


이 경우 노조 사용도 자격이 되나요?
wondra

union오류가 발생하기 쉬운 저수준 메커니즘입니다. variant아마도 내부적으로 사용하고 더 안전하게 사용합니다.
Erlkoenig '12

@wondra union 자체는 현재 활성화 된 멤버를 검사 할 수 없기 때문에 그다지 유용하지 않습니다. std :: string과 같은 사소한 클래스가 아닌 사용자 정의 소멸자가있는 클래스와 함께 사용하면 매우 고통 스럽습니다. 원하는 것은 태그 된 유니온입니다. std :: variant가 구현하는 데이터 구조는 다음과 같습니다.
eerorika

1
libstdc ++ variant 는을 사용 union합니다. 원시 메모리를 사용하고 새로운 배치를 사용하는 대안은 constexpr생성자 에서 사용할 수 없습니다 .
Erlkoenig '12

@Erlkoenig 공정 충분히, 나는 내가 말한 것을 되 찾습니다. 나는 노동 조합을 사용하지 않은 부스트 ​​구현 만 살펴 보았고 모두가 같은 것을 가정했습니다.
eerorika

11

C ++는 정적으로 유형이 지정된 언어 이므로 모든 변수 유형이 런타임 전에 결정됩니다. 따라서 auto키워드는var 동적으로 입력되는 언어 인 javascript의 키워드와 다릅니다. auto키워드는 일반적으로 불필요하게 복잡한 유형을 지정하는 데 사용됩니다.

찾고있는 것은 대신 C ++ 템플릿 클래스를 사용하여 수행 할 수 있으므로 다른 유형의 클래스를 여러 버전으로 만들 수 있습니다.

이 코드는 당신이 찾고있는 대답 일 수 있습니다.

template <typename T>
class Token {
private:
    T value;

public:
    Token(const T& ivalue) {
        value = ivalue;
    }

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

이 코드는 operator<<std :: ostream & 및 T 유형에 대해 함수 를 정의해야하는 것처럼 일부 조건이 충족되면 컴파일됩니다 .


6

다른 사람들이 제안한 것과 다른 접근법은 템플릿을 사용하는 것입니다. 예를 들면 다음과 같습니다.

template<class T>
class Token {
public:

    T value;

    Token(T value) :
        value(std::move(value))
    {}

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

그런 다음 수업을 다음과 같이 사용할 수 있습니다.

Token<int> x(5);
x.printValue();

3

std::variant타입을 사용할 수 있습니다 . 아래 코드는 한 가지 방법을 보여줍니다 (그러나 약간 어색합니다, 인정해야합니다).

#include <iostream>
#include <variant>

class Token {
public:

    std::variant<int, float, std::string> value;

    Token(int ivalue) {
        value = ivalue;
    }
    Token(float fvalue) {
        value = fvalue;
    }
    Token(std::string svalue) {
        value = svalue;
    }

    void printValue() {
        switch (value.index()) {
            case 0:
                std::cout << "The token value is: " << std::get<0>(value) << std::endl;
                break;
            case 1:
                std::cout << "The token value is: " << std::get<1>(value) << std::endl;
                break;
            case 2:
                std::cout << "The token value is: " << std::get<2>(value) << std::endl;
                break;
        }
    }
};

int main() {
    Token it(1);
    Token ft(2.2f);
    Token st("three");
    it.printValue();
    ft.printValue();
    st.printValue();
    return 0;
}

"x" 는 컴파일 타임 상수 표현식이어야합니다. std::get<0>(value)로 작성 될 수 있다면 훨씬 좋을 것 입니다.std::get<value.index()>(value)<x>


1
아마 std::visit대신 사용하는 것이 좋습니다 switch.
eerorika

1

auto 특정 유형으로 추론 가능해야하며 런타임 동적 입력을 제공하지 않습니다.

선언 Token할 때 사용할 수있는 모든 가능한 유형을 알고 std::variant<Type1, Type2, Type3>있다면 "type enum"과 "union"을 갖는 것과 비슷합니다. 적절한 생성자와 소멸자가 호출되도록합니다.

std::variant<int, std::string> v;
v = "example";
v.index(); // 1, a int would be 0
std::holds_alternative<std::string>(v); // true
std::holds_alternative<int>(v); // false
std::get<std::string>(v); // "example"
std::get<int>(v); // throws std::bad_variant_access

Token적절한 가상 방법을 사용하여 각 사례마다 다른 하위 유형 (템플릿 사용 가능) 을 만들 수도 있습니다 .

class Token {
public:
    virtual void printValue()=0;
};

class IntToken : public Token {
public:
    int value;
    IntToken(int ivalue) {
        value = ivalue;
    }
    virtual void printValue()override
    {
        std::cout << "The token value is: " << value << std::endl;
    }
}

0

아래 솔루션은 Fire Lancer의 답변과 비슷합니다. 중요한 차이점은 템플릿을 사용하여 주석을 따르 므로 인터페이스의 파생 인스턴스를 명시 적으로 만들 필요 가 없다는 것 입니다. Token그 자체가 인터페이스 클래스가 아닙니다. 대신, 인터페이스를 내부 클래스로 정의하고 파생 클래스의 정의를 자동화하기위한 내부 템플리트 클래스를 정의합니다.

정의가 지나치게 복잡해 보입니다. 그러나 Token::Base인터페이스를 정의하고 인터페이스에서 Token::Impl<>파생됩니다. 이 내부 클래스는의 사용자에게 완전히 숨겨져 Token있습니다. 사용법은 다음과 같습니다.

Token s = std::string("hello");
Token i = 7;

std::cout << "The token value is: " << s << '\n';
std::cout << "The token value is: " << i << '\n';

또한 아래 솔루션은 변환 연산자를 구현하여 Token인스턴스를 일반 변수 에 할당하는 방법을 보여줍니다 . 에 의존 dynamic_cast하고 캐스트가 유효하지 않은 경우 예외가 발생합니다.

int j = i; // Allowed
int k = s; // Throws std::bad_cast

정의 Token는 다음과 같습니다.

class Token {

    struct Base {
        virtual ~Base () = default;
        virtual std::ostream & output (std::ostream &os) = 0;
    };

    template <typename T>
    struct Impl : Base {
        T val_;
        Impl (T v) : val_(v) {}
        operator T () { return val_; }
        std::ostream & output (std::ostream &os) { return os << val_; }
    };

    mutable std::unique_ptr<Base> impl_;

public:

    template <typename T>
    Token (T v) : impl_(std::make_unique<Impl<T>>(v)) {}

    template <typename T>
    operator T () const { return dynamic_cast<Impl<T>&>(*impl_); }

    friend auto & operator << (std::ostream &os, const Token &t) {
        return t.impl_->output(os);
    }
};

온라인으로 사용해보십시오!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.