Проблема контравариантности в C ++ со стандартными контейнерами - PullRequest
0 голосов
/ 09 мая 2019

Мы с коллегой внедряем Google Test для нашей базы кода и сталкиваемся с несколькими проблемами с Contravariance, касающимися использования стандартных контейнеров шаблонов.

Итак, Google Test требует от нас создания чисто виртуального класса Interface, который отражает наш реальный класс, который будет наследовать интерфейс и реализовывать все виртуальные функции.Это нужно использовать в 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<BaseType>, но мы только заполняем его одним DerivedType.

Почему нам разрешено смешивать два различных производных типа в std::vector<Fruit> в приведенном выше примере кода (т. Е. Apple и Banana), но мы не можем передать std::vector<DerivedType> к функциональному параметру, который принимает std::vector<BaseType>?

Каков наилучший способ обойти эту проблему, касающуюся Google Test и Google Mock.Они говорят, что если производственный код изменяется в соответствии с потребностями тестов, то это, вероятно, не лучшая практика.

Другой способ сделать это - добавить шаблоны для производных типов в любой класс, который определяет их как члены.Выполнение этого было бы довольно большим пересмотром, а затем потребовалось бы, чтобы любой пользователь библиотеки, которую мы создаем, должен был обернуть все экземпляры этих новых классов, которые содержат эти интерфейсные / производные типы, просто чтобы заставить Google Mock работать.

На данный момент мы работаем над устаревшим кодом, который нельзя так сильно изменить, чтобы включить Google Mock.Мы не можем просто пропустить тестирование этих новых типов классов, какой будет лучший метод для продвижения вперед?

1 Ответ

0 голосов
/ 09 мая 2019

Пожалуйста, смотрите код ниже.

#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;
* 1006.* и реализовать его в производном классе - скажем, в классе Apple
virtual string FruitDetailType()
{
    return "Apple, Honey Crisp";
}
//or,
virtual string FruitDetailType()
{
    return appleType;
}
...