C++ 中的集合(异构类型的数组)

Sets (arrays of heterogeneous types) in C++

我想用 C++ 模拟 python 集合。 根据 faq,一种惯用的方法是使用 std::vector 将指针(如果是智能指针则更好)存储到句柄 class,然后由特定类型重载我们想将其存储在模拟集中。

这是我的尝试:

// experimenting with vectors of heterogeneous elements (like sets in python)
#include <iostream>
#include <memory>
#include <string>
#include <vector>


// Handle can wrap an int, a double, or a string.
class Handle {
public:
  Handle();
  virtual int val();
  virtual double val();
  virtual std::string val();
};


class Int : public Handle {
public:
  Int(const int n) : value(n) {};
  int val() override { return value; };
private:
  int value;
};


class Double : public Handle {
public:
  Double(const double n) : value(n) {};
  double val() override { return value; };
private:
  double value;
};


class String : public Handle {
public:
  String(const std::string& n) : value(n) {};
  std::string val() override { return value; };
private:
  std::string value;
};


int main() {
  // v simulates a set with 12 elements of types int, double, or string.
  std::vector<std::shared_ptr<Handle>> v(12);

  for (int i = 0; i < v.size(); i += 3) {
    v[i] = std::shared_ptr<Int>(new Int(i));
    v[i + 1] = std::shared_ptr<Double>(new Double(i + 1.));
    v[i + 2] = std::shared_ptr<String>(new String(std::to_string(i + 2)));
  }

  for (auto& it : v) {
    std::cout << it->val() << std::endl;
  }
}

这种方法比将 v 声明为 std::vector<Handle*> 更可取,因为使用智能指针可以让我忘记手动删除指针,同时实现运行时多态性。

但是上面的代码无法编译,因为我们不允许仅在 return 类型上重载函数(在本例中为 val())。

有没有办法修复我的代码,以便我可以模拟具有不同类型的集合?有没有更好的惯用方法?

编辑

这是我在 n 之后更改代码的方式。 'pronouns' m.的回答。 该代码似乎有效,我很高兴将其作为使用继承和智能指针的练习(即使它不是手头特定问题的最佳解决方案,请参阅 Maxim Egorushkin 的回答以获得更好的方法)。

// experimenting with vectors of heterogeneous elements (like sets in python)
#include <iostream>
#include <memory>
#include <string>
#include <vector>


// Handle can wrap an int, a double, or a string.
class Handle {
public:
  Handle() {};
  virtual void print(std::ostream&) = 0;
};


class Int : public Handle {
public:
  Int(const int n) : value(n) {};
  void print(std::ostream& s) override { s << value << std::endl; };
private:
  int value;
};


class Double : public Handle {
public:
  Double(const double n) : value(n) {};
  void print(std::ostream& s) override { s << value << std::endl; };
private:
  double value;
};


class String : public Handle {
public:
  String(const std::string& n) : value(n) {};
  void print(std::ostream& s) override { s << value << std::endl; };
private:
  std::string value;
};


int main() {
  std::vector<std::shared_ptr<Handle>> v(12);

  for (size_t i = 0; i < v.size(); i += 3) {
    v[i] = std::shared_ptr<Int>(new Int(i));
    v[i + 1] = std::shared_ptr<Double>(new Double(i + 1.));
    v[i + 2] = std::shared_ptr<String>(new String(std::to_string(i + 2)));
  }

  for (const auto& it : v) {
    it->print(std::cout);
  }
}

I would like to simulate python sets in C++. According to the faq, an idiomatic way to do this would be to use an std::vector which stores pointers...

C++ 中的集合是 std::set (often a red-black tree) and std::unordered_set(散列 table)。 std::vector 是一个可调整大小的数组。

如果您知道要存储在集合中的所有类型,请使用 std::variant 作为值,而不是指针。

例如:

#include <unordered_set>
#include <variant>
#include <string>

int main() {
    using Value = std::variant<int, double, std::string>;
    using ValueSet = std::unordered_set<Value>;

    ValueSet s;
    s.insert(1);       // int
    s.insert(3.14);    // double
    s.insert("hello"); // string

    for(auto const& value : s)
        visit([](auto&& v) { std::cout << v << '\n'; }, value);
}

C++ 是一种静态类型 语言。每个表达式都有一个静态确定的类型。这意味着您、tge 编译器和其他所有人都能够仅从程序文本中确定类型,而无需尝试执行任何代码。

(由 tge 表达式表示的对象也可能具有动态类型,在 运行 时确定,但这不是我们关心的)。 A

让我们看看这个表达式。

it->val()

它的类型是什么?

唯一可能的答案是 std::string。的确,it的类型是std::shared_ptr<Handle>Handle::val指定为returnstd::stringHandle 具有派生类型这一事实没有任何作用。无论指向对象的 dynamic 类型是什么,it->val()static 类型都是 std::string,如果某物具有 std::string 的静态类型,它不能具有 double 的动态类型。这就是为什么您不能以这种方式覆盖 val 的原因。

那么我们要根据对象的动态类型打印不同的东西怎么办呢?

标准的 OOP 解决方案是使用 打印 的方法,而不是 return 打印东西的方法。在 C++ 术语中,这将是一个虚函数,例如

virtual void print() = 0;

在每个派生中覆盖 class。

当然现实生活没有那么简单。如果我们需要打印到任意流怎么办?我们需要将流作为参数传递给 print。也许我们还需要 to.pass 一些东西。也许我们想要指定打印字段宽度、精度或其他任何内容。如果我们想做一些除了打印之外的其他事情怎么办?我们是否需要为我们想做的每件事都使用一个单独的方法? OOP 研究这些和更多问题的答案,但我不能在一个单一的 SO post 中回答它们 :(