【问题标题】:C++ Contravariance issue with standard containers标准容器的 C++ 逆变问题
【发布时间】:2019-09-27 13:08:16
【问题描述】:

我和我的同事正在为我们的代码库实施 Google 测试,并且在使用标准模板容器方面遇到了一些关于逆变的问题。

因此,Google Test 要求我们创建一个纯虚拟接口类,该类反映我们的实际类,该类将继承接口并实现所有虚拟功能。这将在 Google Mock 中用于测试。这也是工作的严格要求,否则我们需要向我们所有的类添加模板,这些模板只能是一种类型......这似乎很不直观,只是为了让测试代码工作。

所以我们只是调查了一些显示问题行为的代码:

#include <vector>
#include <string>
#include <iostream>

class Fruit{
public:
    Fruit(std::string colorIn) : color(colorIn) {}
    std::string color;
};

class Apple : public Fruit{
public:
    Apple() :  Fruit("Red"){ appleType = "Honey Crisp"; }
    Apple(const Fruit& fruit) : Fruit(fruit.color) { appleType = "Honey Crisp"; }
    std::string appleType;
};

class Banana : public Fruit{
public:
    Banana() :  Fruit("Yellow"){ bananaType = "Dole"; }
    Banana(const Fruit& fruit) : Fruit(fruit.color) { bananaType = "Dole"; }
    std::string bananaType;
};

void takeMyFruit(std::vector<Fruit>& fruits){
    if(!fruits.empty()){
        std::cout << "Take my " << fruits[0].color << " " << ((Banana)(fruits[0])).bananaType << " banana." << std::endl;
        std::cout << "Take my " << fruits[1].color << " " << ((Apple)(fruits[1])).appleType << " apple." << std::endl;
    }else{
        std::cout << "You gave me an empty bag?" << std::endl;
    }
}

int main(){
    std::vector<Fruit> fruits;
    fruits.push_back(Banana());
    fruits.push_back(Apple());

    std::vector<Banana> bananas = { Banana() };

    std::vector<Apple> apples = { Apple() };

    takeMyFruit(fruits);    //Why can I do this?
    //takeMyFruit(bananas); //Compile error due to contravariance
    //takeMyFruit(apples);  //Compile error due to contravariance

    return 0;
}

我们需要能够编译一些可以接受容器基类型的东西,即std::vector&lt;BaseType&gt;,但我们只用一个DerivedType 填充它。

为什么我们可以在上面我们创建的代码示例中的std::vector&lt;Fruit&gt; 中混合两种不同的派生类型(即AppleBanana),但不能将std::vector&lt;DerivedType&gt; 传递给接受std::vector&lt;BaseType&gt;的功能参数?

解决有关 Google Test 和 Google Mock 的问题的最佳方法是什么。他们说,如果要更改生产代码以适应测试的需要,那么这可能不是最佳实践。

我们看到的另一种方法是将派生类型的模板添加到将它们定义为成员的任何类。这样做将是一个相当大的改造,然后将要求我们正在创建的库的任何用户都必须包装这些包含这些接口/派生类型的新类的每个实例化,以便让 Google Mock 工作。

我们目前正在处理遗留代码,这些代码无法更改太多以合并 Google Mock。我们也不能跳过测试这些新的类类型,前进的最佳方法是什么?

【问题讨论】:

  • 我认为你应该使用vector fruits,而不是vector 或vector> 会是更好的选择
  • 至少它可以保护你免受slicing的伤害。
  • @Tony 在我们的实际代码库中,我们确实使用指针/智能指针作为容器的模板类型。同样的事情也会发生。
  • @FredLarson 当我们在将它们定义为成员的这些新类中使用它们时,我们只使用与所有 DerivedTypes 的 BaseType 通用的功能。只是为了抓住它们,我们的驱动界面知道一切的动态和静态类型,因此不会发生切片。 (即 BaseType 有一个 setPosition 函数,这两种类型的 DerivedTypes 都可以作为成员存储在新类中)。而且我们不能只将 DerivedType 作为成员存储为 BaseType,因为当他们请求该对象时,驱动程序会执行有效的调用。

标签: c++ googletest contravariance googlemock


【解决方案1】:

请看下面的代码。

#include <vector>
#include <string>
#include <iostream>
#include <memory>

using namespace std;

class Fruit
{
    public:
    Fruit(std::string colorIn) : color(colorIn) { }
    std::string color;
};

class Apple : public Fruit{
public:
    Apple() :  Fruit("Red") { appleType = "Honey Crisp"; }
    Apple(const Fruit& fruit) : Fruit(fruit.color) { appleType = "Honey Crisp"; }
std::string appleType;
};

class Banana : public Fruit{
public:
    Banana() :  Fruit("Yellow") { bananaType = "Dole"; }
    Banana(const Fruit& fruit) : Fruit(fruit.color) { bananaType = "Dole"; }
std::string bananaType;
};

void takeMyFruit(std::vector<shared_ptr<Fruit>>& fruits)
{
    if (!fruits.empty())
    {
        for (vector<shared_ptr<Fruit>>::const_iterator it = fruits.begin(); it != fruits.end(); ++it)
            std::cout << "Take my " << (*it)->color;
        // You shouldn't use the following two lines as you don't know what is in the vector.
        //        std::cout << "Take my " << fruits[0]->color << " " << std::dynamic_pointer_cast<Banana>(fruits[0])->bananaType << " banana." << std::endl;
        //        std::cout << "Take my " << fruits[1]->color << " " << std::dynamic_pointer_cast<Apple>(fruits[1])->appleType << " apple." << std::endl;
    }
    else
    {
        std::cout << "You gave me an empty bag?" << std::endl;
    }
}

int main()
{
    std::vector<std::shared_ptr<Fruit>> fruits;
    fruits.push_back(std::make_shared<Banana>());
    fruits.push_back(std::make_shared<Apple>());

    std::vector<std::shared_ptr<Fruit>> bananas = { std::make_shared<Banana>() };

    std::vector<std::shared_ptr<Fruit>> apples = { std::make_shared<Apple>() };

    takeMyFruit(fruits);    //Why can I do this?
    takeMyFruit(bananas); //OK now
    takeMyFruit(apples);  //OK now

    return 0;
}

如果你想有一个显示水果类型的功能,正确的做法是在Fruit中添加一个虚函数

virtual string FruitDetailType() = 0;

并在派生类中实现它 - 比如说苹果类

virtual string FruitDetailType()
{
    return "Apple, Honey Crisp";
}
//or,
virtual string FruitDetailType()
{
    return appleType;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-31
    • 2011-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多