如何避免具有相同类型参数的函数中的错误

时间:2018-08-15 15:19:30

标签: c++ function function-declaration

如何避免将相同类型的参数传递给函数的错误?

让我们考虑一下函数读取一些二进制数据:

std::vector<uint8_t> read(size_t offset, size_t amount);

很容易将偏移量误认为是金额(我做了很多次类似的事情)。

我看到了解决方案:

struct Offset
{
    explicit Offset(size_t value) : value{value}{}
    size_t value;
};
struct Amount
{
    explicit Amount(size_t value) : value{value}{}
    size_t value;
};
std::vector<uint8_t> read(Offset offset, Amount amount);

是否有更好的解决方案来避免此类错误?

2 个答案:

答案 0 :(得分:0)

您可以做的另一件事是在结构中传递参数。这还允许您为这些值设置合理的默认值。当构造函数接受大量参数时,这尤其有用。例如:

class FooService
{
public:
    // parameters struct.
    struct Options
    {
        ip::address listen_address = ip::address::any();
        uint16_t port = 1337;
        bool allow_insecure_requests = false;
        std::string base_directory  = "/var/foo/"
    };
    //constructor takes the options struct to pass values. 
    explicit FooService(FooServiceOptions options);
    // ...
};

然后将其用作:

FooService::Options o;
o.port = 1338;
//all other values keep their defaults. 

auto service = make_unique<FooService>(o);

答案 1 :(得分:0)

我可以想到两种方法。

标记类型

从本质上讲,这就是您在问题中所建议的内容,但我将以通用方式实施。

template <typename Tag, typename T>
struct Tagged
{
  explicit Tagged(const T& value) : value{value} { }
  T value;
};

template <typename Tag, typename T>
Tagged<Tag, T> tag(const T& value)
{
  return Tagged<Tag, T>{value};
}

struct OffsetTag
{ };
struct AmountTag
{ };

using Offset = Tagged<OffsetTag, std::size_t>;
using Amount = Tagged<AmountTag, std::size_t>;

std::vector<uint8_t> read(Offset offset, Amount amount);

这使您可以将相同的概念扩展到其他基础数据类型。

命名参数成语

Named Parameter Idiom与@PaulBelanger的答案中的Options方法有些相似,但是可以在适当的位置使用它,并且不允许用户使用带括号的快捷键来带给您回到您以前遇到的相同问题。但是,它将默认初始化所有参数,因此尽管可以避免混淆参数,但不能强制您为所有参数提供明确的值。例如:

class ReadParams
{
public:
  ReadParams() : m_offset{0}, m_amount{128}
  { }

  ReadParams& offset(std::size_t offset)
  {
    m_offset = offset;
    return *this;
  }

  // Could get rid of this getter if you can make the users
  // of this class friends.
  std::size_t offset() const { return m_offset; }

  ReadParams& amount(std::size_t amount)
  {
    m_amount = amount;
    return *this;
  }

  // Could get rid of this getter if you can make the users
  // of this class friends.
  std::size_t amount() const { return m_amount; }

private:
  std::size_t m_offset;
  std::size_t m_amount;
};

std::vector<uint8_t> read(const ReadParams& params);

int main()
{
  read(ReadParams{}.offset(42).amount(2048)); // clear parameter names
  // read(ReadParams{42, 2048});              // won't compile
  read(ReadParams{}.offset(42));              // also possible, amount uses default value
}

您可以将ReadParams的成员实现为std::optional,如果访问未初始化的成员,则抛出运行时错误;但您不能再强制在编译时强制用户实际提供所有参数。