类设计-使用可选的?变体?不透明?

问题描述 投票:2回答:3

我想为PCI总线位置提供一门课程。为了便于讨论,它们以三种形式出现:

  • [[domain]:[bus]:[device]。[function]
  • [[domain]:[bus]:[device]
  • [[总线]:[设备]。[功能]

并且假设每个字段都是非负整数值(为了简单起见,我们甚至说unsigned)。

关于如何定义此类,我I之以鼻。我可以在域和函数字段中使用std::optional。但是,它们不是both可选的。我可以使用3种类型的变体,但是随后我需要定义单独的类型,这些类型有很多重叠。我可以只保留4个unsigned和一个3值的枚举,而该枚举的格式实际上是有效的-但这很麻烦,我需要使用getter并使该类变得不透明。如果我尝试以某种方式使用工会,也是一样。

似乎我做出的每一个选择都将是一个不稳定的课堂。如何减少对它的不满?

注意:任何语言标准版本都可以解决,尽管我怀疑C ++ 20会给你任何东西。

c++ c++17 class-design
3个回答
1
投票

基于我的评论,我想知道这样的事情是否可行:

enum class pci_format { domain_function, domain, function };

template <pci_format E> struct tag { }; 

class pci_location {
  public:
    pci_location (tag<pci_format::domain_function>, unsigned domain, unsigned bus,
      unsigned device, unsigned function)
      : format_(pci_format::domain_function)
      , domain_(domain)
      , bus_(bus)
      , device_(device)
      , function_(function)
    { }

    // Repeat for other values of pci_format.

    pci_format format () const { return format_; }

    bool has_domain () const {
      return (format_ == pci_format::domain_function)
        or (format_ == pci_format::domain);
    }

    unsigned domain () const {
      if (not has_domain()) { throw std::runtime_error("Domain not available."); }
      return domain_;
    }

    // Repeat for other fields.

  private:
    pci_format format_;
    unsigned domain_;
    unsigned bus_;
    unsigned device_;
    unsigned function_
};

您基本上将为每个PCI“格式”创建一个特定的构造函数。当然,您也可以将每个unsigned存储为std::optional<unsigned>,但这将迫使用户“取消引用”每个可选项,即使他们确定必须包含值也是如此。

一种或另一种方式,他们必须检查位置所在的“格式”,因此在我看来,为此使用枚举更加用户友好。然后,用户只需检查一次即可确切知道哪些字段可用。

我想您可以在所有这些之上放置一个访问者,以便他们可以简单地提供针对每种“格式”执行的代码:

struct pci_location_visitor {
  virtual void visit (tag<pci_format::domain_function>, pci_location const & obj) = 0;
  // Repeat for other enum values.
};

// Add to pci_location:
void accept (pci_location_visitor & visitor) {
  switch (format_) {
    case pci_format::domain_function:
      return visitor.visit(tag<pci_format::domain_function>{}, *this);
    default: throw std::runtime_error("Format not supported for visitation.");
  }
}

然后,您可以创建一个访问者,该访问者可以由一堆可调用对象(即lambda)构成,因此可以按以下方式使用所有访问者:

pci_location const & loc = getIt();
auto printSomething = make_pci_location_visitor(
      [](tag<pci_format::domain_function>, pci_location const & e) { std::cout << e.domain(); }
    , [](tag<pci_format::domain>,          pci_location const & e) { std::cout << e.bus(); }
    , [](tag<pci_format::function>,        pci_location const & e) { std::cout << e.function(); }
  );
loc.accept(printSomething);

有关如何构造这样的访问者的示例,请参见overloaded中的std::visit example on cppreference.com类。


0
投票

根据注释中的要求...鉴于我对C ++ 14没有特别的要求,因此用户更喜欢使用此类,所以我将按照以下方式做一些通用的事情:

std::visit

(您可以在#include <array> #include <climits> #include <iostream> #include <stdexcept> class pci_location_t { public: struct dbdf { unsigned int domain; unsigned int bus; unsigned int device; unsigned int function; }; struct dbd { unsigned int domain; unsigned int bus; unsigned int device; }; struct bdf { unsigned int bus; unsigned int device; unsigned int function; }; pci_location_t(dbdf v) : domain(v.domain), bus(v.bus), device(v.device), function(v.function) {} pci_location_t(dbd v) : domain(v.domain), bus(v.bus), device(v.device), function(INVALID) {} pci_location_t(bdf v) : domain(INVALID), bus(v.bus), device(v.device), function(v.function) {} template <typename dbdf_f, typename dbd_f, typename bdf_f> auto visit(dbdf_f dbdf_fun, dbd_f dbd_fun, bdf_f bdf_fun) const { if (domain == INVALID) { if (function == INVALID) { throw std::domain_error("Wrong PCI location format"); } return bdf_fun(bdf{bus, device, function}); } else if (function == INVALID) { return dbd_fun(dbd{domain, bus, device}); } else { return dbdf_fun(dbdf{domain, bus, device, function}); } } private: friend pci_location_t invalid_location(); pci_location_t() : domain(INVALID), bus(INVALID), device(INVALID), function(INVALID) {} const static unsigned int INVALID = UINT_MAX; unsigned int domain; unsigned int bus; unsigned int device; unsigned int function; }; pci_location_t invalid_location() { return pci_location_t{}; } int main() { std::array<pci_location_t, 4> locations = { pci_location_t(pci_location_t::dbdf{1, 2, 3, 4}), pci_location_t(pci_location_t::dbd{1, 2, 3}), pci_location_t(pci_location_t::bdf{2, 3, 4}), invalid_location() }; try { for (auto& l : locations) { l.visit( [] (auto dbdf) { std::cout << dbdf.domain << ":" << dbdf.bus << ":" << dbdf.device << "." << dbdf.function << std::endl; }, [] (auto dbd) { std::cout << dbd.domain << ":" << dbd.bus << ":" << dbd.device << std::endl; }, [] (auto bdf) { std::cout << bdf.bus << ":" << bdf.device << "." << bdf.function << std::endl; } ); } std::cout << "Done!" << std::endl; } catch(const std::exception& e) { std::cout << e.what() << std::endl; } return 0; } 上进行检查。

如果您不喜欢特殊值,请随意使用可选选项或单独的格式字段。


0
投票

我会将域和函数都设为可选(只要有效,我就不在乎如何做,而只是将唯一缺一的条件强制为类不变式。也就是说,只有可以更改任何字段的功能才需要执行检查并将可能的错误发回给用户。无需使用变体或动态解释的Coliru数组来膨胀代码。吻。

© www.soinside.com 2019 - 2024. All rights reserved.