首页 > 解决方案 > 如何避免具有相同类型参数的函数中的错误

问题描述

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

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

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);

有没有更好的解决方案来避免这样的错误?

标签: c++functionfunction-declaration

解决方案


我能想到两种方法。

标记类型

这基本上就是您在问题中所建议的,但我会一般地实施它。

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
}

如果访问了未初始化的成员,您可以实现ReadParamsasstd::optional的成员并引发运行时错误;但是您不能再在编译时强制用户实际提供所有参数。


推荐阅读