簡體   English   中英

C ++協方差返回類型的缺點是什么?

[英]What are the drawbacks of C++ covariance return types?

我最近不得不處理C ++ 協方差返回類型,例如以下構造:

struct Base
{
     virtual ~Base();
};
struct Derived : public Base {};

struct AbstractFactory
{
    virtual Base *create() = 0;
    virtual ~AbstractFactory();
};

struct ConcreteFactory : public AbstractFactory
{
    virtual Derived *create()
    {
        return new Derived;
    }
};

它允許客戶端代碼在需要時將Derived對象視為Base類型或Derived類型,尤其是在不使用dynamic_caststatic_cast

這種方法的缺點是什么? 這是設計不良的標志嗎?

謝謝。

協方差不適用於智能指針,因此協方差違反了:

切勿通過 C ++核心准則的原始指針(T *)或引用(T&)轉讓所有權 有一些技巧可以限制問題,但是協變值仍然是原始指針。

文檔中的示例:

X* compute(args)    // don't
{
    X* res = new X{};
    // ...
    return res;
}

這幾乎與問題中的代碼正在執行的操作相同:

virtual Derived *create()
{
    return new Derived;
}

不幸的是,對於shared_ptrunique_ptr ,以下內容都是非法的:

struct AbstractFactory
{
    virtual std::shared_ptr<Base> create() = 0;
};

struct ConcreteFactory : public AbstractFactory
{
    /*
      <source>:16:38: error: invalid covariant return type for 'virtual std::shared_ptr<Derived> ConcreteFactory::create()'
    */
    virtual std::shared_ptr<Derived> create()
    {
        return std::make_shared<Derived>();
    }
};

編輯

nm的答案顯示了一種使用一些附加代碼來模擬語言協方差的技術。 它有一些潛在的維護成本,因此在決定走哪條路之前要考慮到這一點。

C ++中實現的協變返回類型的主要限制是它們只能與原始指針和引用一起使用。 沒有真正的理由盡可能使用它們,但是這種限制意味着我們不能在需要它們時總是使用它們。

在不提供語言功能的情況下,很容易克服此限制,同時提供相同的用戶體驗。 這是如何做。

讓我們使用常見和流行的非虛擬接口慣用法重寫我們的類。

struct AbstractFactory
{
    Base *create() {
      return create_impl();
    }

  private:
    virtual Base* create_impl() = 0;
};

struct ConcreteFactory : public AbstractFactory
{
    Derived *create() {
      return create_impl();
    }

  private:
    Derived *create_impl() override {
        return new Derived;
    }
};

現在,這里發生了一些有趣的事情。 create不再是虛擬的,因此可以具有任何返回類型。 它不受協變量返回類型規則的約束。 create_impl仍然受約束,但是它是私有的,除了類本身之外沒有人調用它,因此我們可以輕松地對其進行操作並完全刪除協方差。

struct ConcreteFactory : public AbstractFactory
{
    Derived *create() {
      return create_impl();
    }

  private:
    Base *create_impl() override {
        return create_impl_derived();
    }

    virtual Derived *create_impl_derived() {
        return new Derived;
    }
};

現在, AbstractFactoryConcreteFactory都具有與以前完全相同的接口,並且看不到協變返回類型。 對我們意味着什么? 這意味着我們可以自由使用智能指針。

// replace `sptr` with your favourite kind of smart pointer

struct AbstractFactory
{
    sptr<Base> create() {
      return create_impl();
    }

  private:
    virtual sptr<Base> create_impl() = 0;
};

struct ConcreteFactory : public AbstractFactory
{
    sptr<Derived> create() {
      return create_impl();
    }

  private:
    sptr<Base> create_impl() override {
        return create_impl_derived();
    }

    virtual sptr<Derived> create_impl_derived() {
        return make_smart<Derived>();
    }
};

在這里,我們克服了語言限制,並為類提供了等效的協變返回類型,而不必依賴有限的語言功能。

請注意技術上的傾向。

    sptr<Base> create_impl() override {
        return create_impl_derived();
    }

此函數在此隱式將Derived指針轉換(“轉換”)為Base指針。 如果我們使用該語言提供的協變返回類型,則編譯器會在需要時自動插入這種向上轉換。 不幸的是,該語言僅夠聰明,足以用於原始指針。 對於其他所有事情,我們必須自己做。 幸運的是,如果有點冗長,這相對容易。

(在這種特殊情況下,只返回一個Base指針就可以了。我不討論這個。我假設我們絕對需要像協變返回類型這樣的東西。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM