关于C ++:为具有特定成员变量值的类创建不同类型

Create distinct type for class with specific member variable value

给定一个具有一些枚举的类,该枚举定义了该类的类型,如以下示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Fruit {
 public:

   enum class FruitType {
      AppleType = 0,
      OrangeType = 1,
      BananaType = 2,
   };
   Fruit(FruitType type) : type_(type) {}
   FruitType fruit_type() const { return type_; }

 private:
   FruitType type_;
};

以及派生自该类的类具有相同的枚举:

1
2
3
4
class DriedFruit : public Fruit {
 public:
  // Some Dried specific methods.
};

是否可以通过每种特定的枚举值以某种方式为Fruit和DryFruit定义不同的类型:

1
2
3
4
5
6
class Apple   // Fruit with FruitType = AppleType
class Orange  // Fruit with FruitType = OrangeType
class Banana  // Fruit with FruitType = BananaType
class DriedApple   // DriedFruit with FruitType = AppleType
class DriedOrange  // DriedFruit with FruitType = OrangeType
class DriedBanana  // DriedFruit with FruitType = BananaType

因此3类苹果,橙和香蕉是不同的类型,而3类苹果干,桔子,香蕉干是不同的类型。

我的问题有点类似于如何在C ++中为同一类定义不同的类型,不同之处在于,我想将有关类类型的信息显式存储为类中的枚举成员变量,并为所有不同的类型提供一个通用的基类。

最有效的方法是什么?

编辑:
主要用例如下-在我的应用程序中,有些方法只希望将Apple作为输入,或者只希望将Orange作为输入,并且许多方法都不关心它是哪种水果。

将Fruit传递给仅期望Apple的方法是不安全/晦涩的,与此同时,许多方法并不关心它是哪种类型,因此拥有3种不同的类型也不是一个好选择。

主要工作流程如下:
根据一些输入参数构建一个Fruit,然后
将其传递并作为水果处理,然后在某个时候
如果是Apple,则从Fruit转换为具体的Apple类型,然后进一步处理,从那时起将其类型限制为Apple。


What would be the most efficient way to do that?

您可以使用非类型模板参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum class FruitType {
   AppleType = 0,
   OrangeType = 1,
   BananaType = 2,
};

template <FruitType F>
class Fruit {
 public:
   FruitType fruit_type() const { return F; }
};

using Apple = Fruit<FruitType::AppleType>;
using Banana = Fruit<FruitType::BananaType>;

是否需要实际的基类取决于您。为某些FruitType提供模板专业化可能也足够了。


这是您想做的吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
enum class FruitType
{
    AppleType = 0,
    OrangeType = 1,
    BananaType = 2,
};

class Fruit
{
public:

    virtual FruitType fruit_type() const = 0;
};

class Apple: public Fruit
{
public:

    FruitType fruit_type() const override { return FruitType::AppleType; }
};

class Orange : public Fruit
{
public:

    FruitType fruit_type() const override { return FruitType::OrangeType; }
};

class Banana : public Fruit
{
public:

    FruitType fruit_type() const override { return FruitType::BananaType; }
};

int main()
{
    Fruit *somefruit = new Apple;

    std::cout <<"Is Apple?" << std::boolalpha << (somefruit->fruit_type() == FruitType::AppleType) << std::endl;
    std::cout <<"Is Orange?" << std::boolalpha << (somefruit->fruit_type() == FruitType::OrangeType) << std::endl;
    std::cout <<"Is Banana?" << std::boolalpha << (somefruit->fruit_type() == FruitType::BananaType) << std::endl;

    return 0;
}

印刷品:

1
2
3
Is Apple? true
Is Orange? false
Is Banana? false


关于需求,您的问题非常抽象。

尽管您编辑后的澄清说明了一种方法

The main use case is as follows - in my applications, there are certain methods that only expect Apple as an input, or only expect Orange as an input, and many methods that do not care which fruit it is.

我正在考虑一个基于接口和标签接口的完全不同的系统
(请参阅完整的实时演示)。

首先为所有水果定义一个公共接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// A basic interface common for all fruits
struct IFruit {
  virtual ~IFruit() {}
  virtual std::string category() const = 0;
  virtual std::string common_name() const = 0;
  virtual std::string botanical_name() const = 0;
};

// An overload for the output operator is just nifty
std::ostream& operator<<(std::ostream& os, const IFruit& fruit) {
    os <<"Category       :" << fruit.category() << std::endl;
    os <<"Common Name    :" << fruit.common_name() << std::endl;
    os <<"Botanical Name :" << fruit.botanical_name() << std::endl;
    return os;
}

定义标签接口以区分您的特定类型(苹果,橙子):

1
2
3
4
5
6
7
8
// Tag interfaces to distinguish (not necessarily empty)
struct IApple : public IFruit {
  virtual ~IApple() {}
};

struct IOrange : public IFruit {
  virtual ~IOrange () {}
};

这些应该要求隐式实现IFruit接口。

现在,您可以提供一个实现IFruit接口的抽象基类。
从构造器函数对public范围隐藏的意义上说,该基类是抽象的,并且需要由继承的类构造器调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Abstract base class implementation
template<class TagInterface>
class FruitBase : public TagInterface {
protected:
      std::string category_;
      std::string common_name_;
      std::string botanical_name_;

      FruitBase ( const std::string& category
                , const std::string& common_name
                , const std::string botanical_name)
          : category_(category), common_name_(common_name)
          , botanical_name_(botanical_name)
      {}

public:
      virtual ~FruitBase () {}
      virtual std::string category() const { return category_; }
      virtual std::string common_name() const { return common_name_; }
      virtual std::string botanical_name() const { return botanical_name_; }
};

根据需要添加其他标签接口:

1
2
3
4
 struct IDriedApple : public IApple {
     virtual ~IDriedApple() {}
     virtual int rest_humidity() const = 0;
 };

现在,您可以使用非常狭窄的类定义来创建具体的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Concrete apples
struct Boskop : public FruitBase<IApple> {
public:
     Boskop() : FruitBase("Apples","Boskop","Malus domestica 'Belle de Boskoop'") {}
};

struct Braeburn : public FruitBase<IApple> {
public:
     Braeburn() : FruitBase("Apples","Braeburn","Malus domestica") {}
};

// Concrete oranges
struct Valencia : public FruitBase<IOrange> {
public:
     Valencia() : FruitBase("Oranges","Valencia","Citrus × sinensis") {}
};

struct Navel : public FruitBase<IOrange> {
public:
     Navel() : FruitBase("Oranges","Navel","Citrus × sinensis") {}
};

我假设您的函数声明专门用于仅使用Apple或Oranges:

1
2
3
4
5
6
7
8
9
void aFunctionThatTakesOnlyApples(IApple& anApple) {
    std::cout <<"This is an apple:" << std::endl;
    std::cout << anApple;
}

void aFunctionThatTakesOnlyOranges(IOrange& anOrange) {
    std::cout <<"This is an orange:" << std::endl;
    std::cout << anOrange << std::endl;
}

这是一个简单的模板函数,用于查询IFruit的已知实例以实现特定的标记接口:
模板
TagInterface * queryTagInterface(IFruit * fruit){
返回dynamic_cast(fruit);
}

这就是您如何实际使用所有这些方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main() {
    std::vector<std::unique_ptr<IFruit>> allFruits;
    allFruits.push_back(std::make_unique<Boskop>());
    allFruits.push_back(std::make_unique<Braeburn>());
    allFruits.push_back(std::make_unique<Valencia>());
    allFruits.push_back(std::make_unique<Navel>());
    for(auto& fruit : allFruits) {
        if(IApple* anApple = queryTagInterface<IApple>(fruit.get())) {
            aFunctionThatTakesOnlyApples(*anApple);
        }
        if(IOrange* anOrange = queryTagInterface<IOrange>(fruit.get())) {
            aFunctionThatTakesOnlyOranges(*anOrange);
        }
        std::cout <<"-----------------------------------------------" << std::endl;
    }    
}

It feels unsafe/obscure to pass Fruit to method that only expects Apple, at the same time there are many methods that do not care which type it is so having 3 distinct types is not a good option either.

我应该注意的是,我仍然不明白是什么使苹果和橙子与它们真正应有的水果不同。但是,这可能适合于多态类设计的抽象隐喻,对于具体的类层次结构设计很有用。