c++函数参数安全

C++ function argument safety

本文关键字:安全 参数 函数 c++      更新时间:2023-10-16

在接受多个相同类型参数的函数中,如何保证调用者不会弄乱顺序?

例如

void allocate_things(int num_buffers, int pages_per_buffer, int default_value ...

// uhmm.. lets see which was which uhh..
allocate_things(40,22,80,...

一个典型的解决方案是将参数放在一个结构中,并使用命名字段。

AllocateParams p;
p.num_buffers = 1;
p.pages_per_buffer = 10;
p.default_value = 93;
allocate_things(p);
当然,您不必使用字段。

如果您有c++ 11编译器,您可以将用户定义的字面值与用户定义的类型结合使用。下面是一个简单的方法:

struct num_buffers_t {
    constexpr num_buffers_t(int n) : n(n) {}  // constexpr constructor requires C++14
    int n;
};
struct pages_per_buffer_t {
    constexpr pages_per_buffer_t(int n) : n(n) {}
    int n;
};
constexpr num_buffers_t operator"" _buffers(unsigned long long int n) {
    return num_buffers_t(n);
}
constexpr pages_per_buffer_t operator"" _pages_per_buffer(unsigned long long int n) {
    return pages_per_buffer_t(n);
}
void allocate_things(num_buffers_t num_buffers, pages_per_buffer_t pages_per_buffer) {
    // do stuff...
}
template <typename S, typename T>
void allocate_things(S, T) = delete; // forbid calling with other types, eg. integer literals
int main() {
    // now we see which is which ...
    allocate_things(40_buffers, 22_pages_per_buffer);
    // the following does not compile (see the 'deleted' function):
    // allocate_things(40, 22);
    // allocate_things(40, 22_pages_per_buffer);
    // allocate_things(22_pages_per_buffer, 40_buffers);
}

目前为止有两个不错的答案,还有一个:另一种方法是尽可能地利用类型系统,并创建强类型定义。例如,使用boost strong typepedef (http://www.boost.org/doc/libs/1_61_0/libs/serialization/doc/strong_typedef.html)。

BOOST_STRONG_TYPEDEF(int , num_buffers);
BOOST_STRONG_TYPEDEF(int , num_pages);
void func(num_buffers b, num_pages p);

调用带参数顺序错误的func现在会导致编译错误。

关于这一点有几点注意。首先,boost的强类型定义方法相当过时;使用可变的CRTP可以做更好的事情,并且完全避免使用宏。其次,这显然会带来一些开销,因为您经常需要显式地进行转换。所以一般来说,你不想过度使用它。对于那些在你的图书馆里反复出现的东西来说,这真的很好。对于一次性出现的事情来说不太好。例如,如果你正在编写一个GPS库,你应该有一个强double类型的pedef来表示以米为单位的距离,一个强int64类型的pedef来表示以纳秒为单位的时间,等等。

(注:帖子最初标记为'C ')

C99以后允许扩展到@Dietrich Epp的想法:复合文字

struct things {
  int num_buffers;
  int pages_per_buffer;
  int default_value 
};
allocate_things(struct things);
// Use a compound literal
allocate_things((struct things){.default_value=80, .num_buffers=40, .pages_per_buffer=22});

甚至可以传递结构的地址。

allocate_things(struct things *);
// Use a compound literal
allocate_things(&((struct things){.default_value=80,.num_buffers=40,.pages_per_buffer=22}));

你不能。这就是为什么建议使用尽可能少的函数参数。

在您的示例中,您可以有单独的功能,如set_num_buffers(int num_buffers), set_pages_per_buffer(int pages_per_buffer)等。

你可能已经注意到allocate_things不是一个好名字,因为它没有表达函数实际在做什么。特别是我不希望它设置一个默认值

为了完整起见,当调用变成。时,可以使用命名参数

void allocate_things(num_buffers=20, pages_per_buffer=40, default_value=20);
// or equivalently
void allocate_things(pages_per_buffer=40, default_value=20, num_buffers=20);

然而,在当前的c++中,这需要相当多的代码来实现(在头文件中声明allocate_things(),它还必须声明适当的外部对象num_buffers等,提供operator=返回一个唯一的合适对象)。

----------工作示例(用于sergej)

#include <iostream>
struct a_t { int x=0; a_t(int i): x(i){} };
struct b_t { int x=0; b_t(int i): x(i){} };
struct c_t { int x=0; c_t(int i): x(i){} };
// implement using all possible permutations of the arguments.
// for many more argumentes better use a varidadic template.
void func(a_t a, b_t b, c_t c)
{ std::cout<<"a="<<a.x<<" b="<<b.x<<" c="<<c.x<<std::endl; }
inline void func(b_t b, c_t c, a_t a) { func(a,b,c); }
inline void func(c_t c, a_t a, b_t b) { func(a,b,c); }
inline void func(a_t a, c_t c, b_t b) { func(a,b,c); }
inline void func(c_t c, b_t b, a_t a) { func(a,b,c); }
inline void func(b_t b, a_t a, c_t c) { func(a,b,c); }
struct make_a { a_t operator=(int i) { return {i}; } } a;
struct make_b { b_t operator=(int i) { return {i}; } } b;
struct make_c { c_t operator=(int i) { return {i}; } } c;
int main()
{
  func(b=2, c=10, a=42);
}

你真的要尝试QA任意整数的所有组合吗?然后把所有的负/零值检查都扔进去?

只需创建两个枚举类型,分别用于最小、中等和最大缓冲区数量,以及较小、中等和较大的缓冲区大小。然后让编译器完成工作,让你的QA人员休息一个下午:

allocate_things(MINIMUM_BUFFER_CONFIGURATION, LARGE_BUFFER_SIZE, 42);

那么你只需要测试有限数量的组合,你就会有100%的覆盖率。5年后为你的代码工作的人只需要知道他们想要实现什么,而不必猜测他们可能需要的数字或哪些值实际上已经在该领域进行了测试。

它确实使代码稍微难以扩展,但听起来这些参数是用于低级性能调优的,因此不应将摆弄这些值视为廉价/琐碎/不需要彻底测试。变更的代码审查Allocate_something (25,25,25);

allocate_something(30, 80, 42);

…可能只是耸耸肩/被甩掉,但是对一个新的枚举值EXTRA_LARGE_BUFFERS的代码审查可能会引发所有关于内存使用、文档、性能测试等的正确讨论。