ProgramingTip

가상 할당 연산자 C ++

bestdevel 2020. 11. 18. 09:33
반응형

가상 할당 연산자 C ++


C ++의 할당 연산자는 가상으로 만들 수 있습니다. 왜 필요한가요? 다른 운영자도 가상으로 만들 수 있습니까?


할당 연산자는 가상으로 만들 필요가 없습니다.

아래 설명은에 대한 내용 operator=이지만 해당 유형을받는 연산자 오버로딩과 해당 유형을받는 모든 함수에도 적용됩니다.

아래 논의는 가상 키워드가 일치하는 함수 서명을 찾는 것과 관련하여 변수의 상속에 대해 알지 못함을 보여줍니다. 마지막 예제에서는 상속 된 유형을 처리 할 때 할당을 처리하는 방법을 보여줍니다.


가상 함수는 상속 변수의 상속에 대해 알지 못합니다.

가상 기능이 작동 광고 함수의 서명이 동일해야합니다. operator =가 가상으로 만들어 지므로 operator =의 변수와 반환 값이 다르기 때문에 호출이 D에서 가상 함수로 작동하지 않습니다.

고유 한 함수 B::operator=(const B& right)D::operator=(const D& right)100 % 완전히 다른 2 개 함수로 볼 수 있습니다.

class B
{
public:
  virtual B& operator=(const B& right)
  {
    x = right.x;
    return *this;
  }

  int x;

};

class D : public B
{
public:
  virtual D& operator=(const D& right)
  {
    x = right.x;
    y = right.y;
    return *this;
  }
  int y;
};

상관 및 오버로드 된 연산자 2 개 :

B 유형의 변수에 할당 될 때 D의 사용을 정의 할 수 있습니다. 이것은 B 변수가 실제로 B의 참조에 D 인 경우에도 마찬가지입니다. D::operator=(const D& right)함수.

아래의 경우, 2 개의 B 참조 D::operator=(const B& right)유전자 유전자가 할당됩니다.

//Use same B as above

class D : public B
{
public:
  virtual D& operator=(const D& right)
  {
    x = right.x;
    y = right.y;
    return *this;
  }


  virtual B& operator=(const B& right)
  {
    x = right.x;
    y = 13;//Default value
    return *this;
  }

  int y;
};


int main(int argc, char **argv) 
{
  D d1;
  B &b1 = d1;
  d1.x = 99;
  d1.y = 100;
  printf("d1.x d1.y %i %i\n", d1.x, d1.y);

  D d2;
  B &b2 = d2;
  b2 = b1;
  printf("d2.x d2.y %i %i\n", d2.x, d2.y);
  return 0;
}

인쇄물 :

d1.x d1.y 99 100
d2.x d2.y 99 13

D::operator=(const D& right)사용되지 않는 것을 보여줍니다 .

가상 키워드가 키워드 B::operator=(const B& right)와 동일한 결과를 얻을 수 있습니다. 즉 그것은B::operator=(const B& right)


모든 것을 하나로 묶는 마지막 단계 인 RTTI :

RTTI를 사용하여 유형을 취하는 가상 기능을 입력하게 처리 할 수 ​​있습니다. 다음은 상속 된 유형을 다룰 때 할당을 처리하는 방법을 알아내는 퍼즐의 마지막 조각입니다.

virtual B& operator=(const B& right)
{
  const D *pD = dynamic_cast<const D*>(&right);
  if(pD)
  {
    x = pD->x;
    y = pD->y;
  }
  else
  {
    x = right.x;
    y = 13;//default value
  }

  return *this;
}

운영자에 따라 증가합니다.

연산자를 가상으로 만드는 요점은 더 많은 필드를 복사하도록 재정의 할 수있는 할당을 누릴 수있는 것입니다.

따라서 기본 및 동적 유형이 파생되고 더 많은 필드가있는 경우 올바른 항목이 복사됩니다.

그러나 LHS가 Derived이고 RHS가 Base라는 위험이 있으므로 가상 연산자가 Derived에서 실행될 때 매개 변수는 Derived가없는 필드를 사용하지 않습니다.

다음은 좋은 토론입니다. http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html


Brian R. Bondy 는 다음과 같이 썼습니다.


모든 것을 하나로 묶는 마지막 단계 인 RTTI :

RTTI를 사용하여 유형을 취하는 가상 기능을 입력하게 처리 할 수 ​​있습니다. 다음은 상속 된 유형을 다룰 때 할당을 처리하는 방법을 알아내는 퍼즐의 마지막 조각입니다.

virtual B& operator=(const B& right)
{
  const D *pD = dynamic_cast<const D*>(&right);
  if(pD)
  {
    x = pD->x;
    y = pD->y;
  }
  else
  {
    x = right.x;
    y = 13;//default value
  }

  return *this;
}

이 솔루션에 몇 가지 언급을 추가하고 싶습니다. 할당 연산자를 위와 동일하게 선언하면 세 가지 문제가 있습니다.

컴파일러는 가상이 아니고 사용자가 생각하는대로 수행하지 않는 const D & 인수를 사용하는 할당 연산자를 생성합니다 .

두 번째 문제는 반환 유형입니다. 파생 인스턴스에 대한 기본 참조를 반환합니다. 어쨌든 코드가 작동 문제가 많지 않을 것입니다. 그래도 그에 따라 참조를 반환하는 것이 좋습니다.

세 번째 문제는 파생 유형 할당 연산자가 기본 클래스 할당 연산자를 호출하지 않습니다. 할당 연산자를 가상으로 선언하면 컴파일러가 자동으로 생성합니다. 이는 원하는 결과를 발생하는 할당 연산자에 대해 두 번 이상의 발생가 발생하지 않는 부작용입니다.

기본 클래스를 고려하면 (제가 인용 한 게시물의 것과 동일) :

class B
{
public:
    virtual B& operator=(const B& right)
    {
        x = right.x;
        return *this;
    }

    int x;
};

다음 코드는 내가 인용 한 RTTI 솔루션을 완성합니다.

class D : public B{
public:
    // The virtual keyword is optional here because this
    // method has already been declared virtual in B class
    /* virtual */ const D& operator =(const B& b){
        // Copy fields for base class
        B::operator =(b);
        try{
            const D& d = dynamic_cast<const D&>(b);
            // Copy D fields
            y = d.y;
        }
        catch (std::bad_cast){
            // Set default values or do nothing
        }
        return *this;
    }

    // Overload the assignment operator
    // It is required to have the virtual keyword because
    // you are defining a new method. Even if other methods
    // with the same name are declared virtual it doesn't
    // make this one virtual.
    virtual const D& operator =(const D& d){
        // Copy fields from B
        B::operator =(d);
        // Copy D fields
        y = d.y;
        return *this;
    }

    int y;
};

이것은 말할 수 있습니다. D에서 도출 할 때 const B & 를 사용하는 1 연산자 =, const D &를 사용 하는 1 연산자 = const D2 &를 사용 하는 하나의 연산자 가 필요하기 때문에 완전한 솔루션이 아닙니다 . 결론은 분명합니다. 연산자 = () 오버로드의 수는 수퍼 클래스의 수 + 1과 가변적입니다.

D2가 D를 상속한다는 것을 고려하여 두 개의 상속 된 연산자 = () 메소드가 어떻게 생겼는지 보았습니다.

class D2 : public D{
    /* virtual */ const D2& operator =(const B& b){
        D::operator =(b); // Maybe it's a D instance referenced by a B reference.
        try{
            const D2& d2 = dynamic_cast<const D2&>(b);
            // Copy D2 stuff
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }

    /* virtual */ const D2& operator =(const D& d){
        D::operator =(d);
        try{
            const D2& d2 = dynamic_cast<const D2&>(d);
            // Copy D2 stuff
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }
};

= (const D2 &) 연산자가 필드를 복사 하는 것이 분명합니다 . 마치 거기에있는 것처럼 상상 상상. 상속 된 연산자 = () 오버로드에서 패턴을 확인할 수 있습니다. 슬프게도 우리는이 패턴을 처리 할 가상 템플릿 메서드를 정의 할 수 없습니다. 내가 본 유일한 솔루션 인 완전한 다형성 할당 연산자를 얻으려면 동일한 코드를 여러 번 복사하여 넣어야합니다. 다른 이항 연산자에도 적용됩니다.


편집하다

주석에서 정의 된 내용했듯이, 삶을 더 쉽게 만들기 위해 할 수있는 최소한의 방법은 최상위 슈퍼 클래스 할당 연산자 = ()를하고 다른 모든 슈퍼 클래스 연산자 = () 메서드에서 호출하는 것입니다. 또한 필드를 복사 할 때 _copy 메소드를 정의 할 수 있습니다.

class B{
public:
    // _copy() not required for base class
    virtual const B& operator =(const B& b){
        x = b.x;
        return *this;
    }

    int x;
};

// Copy method usage
class D1 : public B{
private:
    void _copy(const D1& d1){
        y = d1.y;
    }

public:
    /* virtual */ const D1& operator =(const B& b){
        B::operator =(b);
        try{
            _copy(dynamic_cast<const D1&>(b));
        }
        catch (std::bad_cast){
            // Set defaults or do nothing.
        }
        return *this;
    }

    virtual const D1& operator =(const D1& d1){
        B::operator =(d1);
        _copy(d1);
        return *this;
    }

    int y;
};

class D2 : public D1{
private:
    void _copy(const D2& d2){
        z = d2.z;
    }

public:
    // Top-most superclass operator = definition
    /* virtual */ const D2& operator =(const B& b){
        D1::operator =(b);
        try{
            _copy(dynamic_cast<const D2&>(b));
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }

    // Same body for other superclass arguments
    /* virtual */ const D2& operator =(const D1& d1){
        // Conversion to superclass reference
        // should not throw exception.
        // Call base operator() overload.
        return D2::operator =(dynamic_cast<const B&>(d1));
    }

    // The current class operator =()
    virtual const D2& operator =(const D2& d2){
        D1::operator =(d2);
        _copy(d2);
        return *this;
    }

    int z;
};

기본 연산자 = () 오버로드에서 하나의 호출 만 수신하므로 set defaults 메서드 가 필요하지 않습니다 . 필드 복사시 변경 사항이 한곳에서 수행되고 모든 연산자 = () 오버로드가 영향을 받고 의도 된 목적을 수행합니다.

덕분에 sehe 제안합니다.


가상 할당은 아래 시나리오에서 사용됩니다.

//code snippet
Class Base;
Class Child :public Base;

Child obj1 , obj2;
Base *ptr1 , *ptr2;

ptr1= &obj1;
ptr2= &obj2 ;

//Virtual Function prototypes:
Base& operator=(const Base& obj);
Child& operator=(const Child& obj);

사례 1 : obj1 = obj2;

이 가상 개념에서 우리 operator=Child수업 을 부르는 것처럼 어떤 역할도하지 않습니다 .

사례 2 & 3 : * ptr1 = obj2;
                  * ptr1 = * ptr2;

여기서 할당은 예상대로되지 않습니다. 대신 수업 operator=에서 이유 가 호출 Base됩니다.

다음 중 하나를 사용하여 수정할 수 있습니다.
1) 주조

dynamic_cast<Child&>(*ptr1) = obj2;   // *(dynamic_cast<Child*>(ptr1))=obj2;`
dynamic_cast<Child&>(*ptr1) = dynamic_cast<Child&>(*ptr2)`

2) 가상 개념

이제 virtual Base& operator=(const Base& obj)서명이 ChildBase에서 다르기 때문에 단순히 사용 하면 도움이되지 않습니다 operator=.

Base& operator=(const Base& obj)일반적인 Child& operator=(const Child& obj)정의 와 함께 Child 클래스 를 추가해야합니다 . 기본 할당 연산자가 없으면 나중에 정의를 포함하는 것이 중요합니다 ( obj1=obj2원하는 결과를 제공하지 않을 수 있음).

Base& operator=(const Base& obj)
{
    return operator=(dynamic_cast<Child&>(const_cast<Base&>(obj)));
}

사례 4 : obj1 = * ptr2;

이 경우 컴파일러는 Child에서 호출 operator=(Base& obj)Child대로 정의를 찾습니다 operator=. 그 존재하지 및하지만 이후 Base유형으로 승격 할 수없는 child암시, 그것은 착오를 통해 것입니다. (캐스팅이 같은 필요합니다 obj1=dynamic_cast<Child&>(*ptr1);)

case2 & 3에 따라 구현하면이 시나리오가 처리됩니다.

보시다시피 가상 할당은 Base 클래스 pointers / reference를 사용하는 할당의 경우 호출을 더 우아하게 만듭니다.

다른 운영자도 가상으로 만들 수 있습니까?


클래스에서 파생 된 클래스가 모든 멤버를 올바르게 복사하도록 보장하려는 경우에만 필요합니다. 다형성으로 아무것도하지 않는다면, 이것에 대해 걱정할 필요가 없습니다.

원하는 연산자를 가상화하는 데 방해가되는 것은 없습니다. 특수한 경우의 메서드 호출 일뿐입니다.

이 페이지 모든 것이 어떻게 작동하는지에 대한 훌륭하고 상세한 설명을 제공합니다.


연산자는 특수 구문을 사용하는 방법입니다. 다른 방법과 같이 치료할 수 있습니다.

참고 URL : https://stackoverflow.com/questions/669818/virtual-assignment-operator-c

반응형