IT TIP

스마트 포인터와 함께 공변 반환 유형을 어떻게 사용할 수 있습니까?

itqueen 2020. 11. 28. 13:21
반응형

스마트 포인터와 함께 공변 반환 유형을 어떻게 사용할 수 있습니까?


다음과 같은 코드가 있습니다.

class RetInterface {...}

class Ret1: public RetInterface {...}

class AInterface
{
  public:
     virtual boost::shared_ptr<RetInterface> get_r() const = 0;
     ...
};

class A1: public AInterface
{
  public:
     boost::shared_ptr<Ret1> get_r() const {...}
     ...
};

이 코드는 컴파일되지 않습니다.

비주얼 스튜디오에서는

C2555 : 가상 함수 반환 유형 재정의가 다르며 공변이 아님

boost::shared_ptr원시 포인터를 사용하지 않고 반환하면 코드가 컴파일됩니다 ( 이는 C ++의 공변 반환 유형 때문이라는 것을 이해합니다 ). 나는 문제 때문에입니다 볼 수 boost::shared_ptrRet1에서 파생되지 않은 boost::shared_ptrRetInterface. 그러나 나는 돌아가려면 boost::shared_ptrRet1다른 내가 복귀 후 반환 값을 캐스팅해야하며, 다른 클래스에서 사용.

  1. 내가 뭘 잘못하고 있니?
  2. 그렇지 않다면 왜 이런 언어가이 시나리오에서 스마트 포인터 간의 변환을 처리 할 수 ​​있도록 확장 가능해야합니까? 바람직한 해결 방법이 있습니까?

첫째, 이것이 실제로 C ++에서 작동하는 방식입니다. 파생 클래스의 가상 함수 반환 유형은 기본 클래스와 동일해야합니다. 특정 클래스 X에 대한 참조 / 포인터를 반환하는 함수는 X에서 파생 된 클래스에 대한 참조 / 포인터를 반환하는 함수에 의해 재정의 될 수 있다는 특별한 예외가 있습니다. 그러나 이것은 스마트 포인터를 허용하지 않습니다. (예 shared_ptr:), 일반 포인터 전용입니다.

인터페이스 RetInterface가 충분히 포괄적 인 경우 호출 코드에서 실제 반환 된 유형을 알 필요가 없습니다. 일반적으로 어차피 의미가 없습니다. 그 이유 get_rvirtual처음에 함수가 기본 클래스에 대한 참조 또는 포인터를 통해 호출하기 때문 AInterface입니다.이 경우 파생 된 클래스가 어떤 유형인지 알 수 없습니다. 반환. 실제 A1참조로 이것을 호출하는 경우 필요한 작업을 수행 하는 별도의 get_r1함수를 만들 수 있습니다 A1.

class A1: public AInterface
{
  public:
     boost::shared_ptr<RetInterface> get_r() const
     {
         return get_r1();
     }
     boost::shared_ptr<Ret1> get_r1() const {...}
     ...
};

또는 방문자 패턴이나 동적 이중 디스패치 기법 과 같은 것을 사용하여 반환 된 객체에 콜백을 전달한 다음 올바른 유형으로 콜백을 호출 할 수 있습니다.


이 솔루션은 어떻습니까?

template<typename Derived, typename Base>
class SharedCovariant : public shared_ptr<Base>
{
public:

typedef Base BaseOf;

SharedCovariant(shared_ptr<Base> & container) :
    shared_ptr<Base>(container)
{
}

shared_ptr<Derived> operator ->()
{
    return boost::dynamic_pointer_cast<Derived>(*this);
}
};

예 :

struct A {};

struct B : A {};

struct Test
{
    shared_ptr<A> get() {return a_; }

    shared_ptr<A> a_;
};

typedef SharedCovariant<B,A> SharedBFromA;

struct TestDerived : Test
{
    SharedBFromA get() { return a_; }
};

C ++에서 메서드를 오버로드 할 때 반환 유형 (비 포인터, 비 참조 반환 유형의 경우)을 변경할 수 없습니다. A1::get_r을 반환해야합니다 boost::shared_ptr<RetInterface>.

Anthony Williams는 훌륭하고 포괄적 인 답변을 제공 합니다.


내 시도는 다음과 같습니다.

template<class T>
class Child : public T
{
public:
    typedef T Parent;
};

template<typename _T>
class has_parent
{
private:
    typedef char                        One;
    typedef struct { char array[2]; }   Two;

    template<typename _C>
    static One test(typename _C::Parent *);
    template<typename _C>
    static Two test(...);

public:
    enum { value = (sizeof(test<_T>(nullptr)) == sizeof(One)) };
};

class A
{
public :
   virtual void print() = 0;
};

class B : public Child<A>
{
public:
   void print() override
   {
       printf("toto \n");
   }
};

template<class T, bool hasParent = has_parent<T>::value>
class ICovariantSharedPtr;

template<class T>
class ICovariantSharedPtr<T, true> : public ICovariantSharedPtr<typename T::Parent>
{
public:
   T * get() override = 0;
};

template<class T>
class ICovariantSharedPtr<T, false>
{
public:
    virtual T * get() = 0;
};

template<class T>
class CovariantSharedPtr : public ICovariantSharedPtr<T>
{
public:
    CovariantSharedPtr(){}

    CovariantSharedPtr(std::shared_ptr<T> a_ptr) : m_ptr(std::move(a_ptr)){}

    T * get() final
   {
        return m_ptr.get();
   }
private:
    std::shared_ptr<T> m_ptr;
};

그리고 약간의 예 :

class UseA
{
public:
    virtual ICovariantSharedPtr<A> & GetPtr() = 0;
};

class UseB : public UseA
{
public:
    CovariantSharedPtr<B> & GetPtr() final
    {
        return m_ptrB;
    }
private:
    CovariantSharedPtr<B> m_ptrB = std::make_shared<B>();
};

int _tmain(int argc, _TCHAR* argv[])
{
    UseB b;
    UseA & a = b;
    a.GetPtr().get()->print();
}

설명 :

이 솔루션은 메타 프로그래밍을 의미하고 공변 스마트 포인터에 사용되는 클래스를 수정합니다.

간단한 템플릿 구조체 Child는 유형 Parent과 상속 을 바인딩하기 위해 여기에 있습니다. 상속 모든 클래스 Child<T>에서 상속 T과 정의 TParent. 공변 스마트 포인터에서 사용되는 클래스는이 유형을 정의해야합니다.

The class has_parent is used to detect at compile time if a class defines the type Parent or not. This part is not mine, I used the same code as to detect if a method exists (see here)

As we want covariance with smart pointers, we want our smart pointers to mimic the existing class architecture. It's easier to explain how it works in the example.

When a CovariantSharedPtr<B> is defined, it inherits from ICovariantSharedPtr<B>, which is interpreted as ICovariantSharedPtr<B, has_parent<B>::value>. As B inherits from Child<A>, has_parent<B>::value is true, so ICovariantSharedPtr<B> is ICovariantSharedPtr<B, true> and inherits from ICovariantSharedPtr<B::Parent> which is ICovariantSharedPtr<A>. As A has no Parent defined, has_parent<A>::value is false, ICovariantSharedPtr<A> is ICovariantSharedPtr<A, false> and inherits from nothing.

The main point is as Binherits from A, we have ICovariantSharedPtr<B>inheriting from ICovariantSharedPtr<A>. So any method returning a pointer or a reference on ICovariantSharedPtr<A> can be overloaded by a method returning the same on ICovariantSharedPtr<B>.


There is a neat solution posted in this blog post (from Raoul Borges)

An excerpt of the bit prior to adding support for mulitple inheritance and abstract methods is:

template <typename Derived, typename Base>
class clone_inherit<Derived, Base> : public Base
{
public:
   std::unique_ptr<Derived> clone() const
   {
      return std::unique_ptr<Derived>(static_cast<Derived *>(this->clone_impl()));
   }

private:
   virtual clone_inherit * clone_impl() const override
   {
      return new Derived(*this);
   }
};

class concrete: public clone_inherit<concrete, cloneable>
{
};

int main()
{
   std::unique_ptr<concrete> c = std::make_unique<concrete>();
   std::unique_ptr<concrete> cc = b->clone();

   cloneable * p = c.get();
   std::unique_ptr<clonable> pp = p->clone();
}

I would encourage reading the full article. Its simply written and well explained.


Mr Fooz answered part 1 of your question. Part 2, it works this way because the compiler doesn't know if it will be calling AInterface::get_r or A1::get_r at compile time - it needs to know what return value it's going to get, so it insists on both methods returning the same type. This is part of the C++ specification.

For the workaround, if A1::get_r returns a pointer to RetInterface, the virtual methods in RetInterface will still work as expected, and the proper object will be deleted when the pointer is destroyed. There's no need for different return types.


maybe you could use an out parameter to get around "covariance with returned boost shared_ptrs.

 void get_r_to(boost::shared_ptr<RetInterface>& ) ...

since I suspect a caller can drop in a more refined shared_ptr type as argument.

참고URL : https://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers

반응형