C++函数参数安全性

Ano*_*ity 58 c++ c++14

在一个采用相同类型的多个参数的函数中,我们如何保证调用者不会搞乱排序?

例如

void allocate_things(int num_buffers, int pages_per_buffer, int default_value ...
Run Code Online (Sandbox Code Playgroud)

然后

// uhmm.. lets see which was which uhh..
allocate_things(40,22,80,...
Run Code Online (Sandbox Code Playgroud)

Die*_*Epp 69

典型的解决方案是将参数放在具有命名字段的结构中.

AllocateParams p;
p.num_buffers = 1;
p.pages_per_buffer = 10;
p.default_value = 93;
allocate_things(p);
Run Code Online (Sandbox Code Playgroud)

当然,您不必使用字段.您可以使用成员函数或任何您喜欢的函数.

  • @FrankPuffer:我认为很明显这些只是占位符名称. (11认同)
  • @FrankPuffer:是的,同意了,但这不是代码审查堆栈交换.如果您对原作者的代码有评论,则它们属于对问题的评论,而不是答案.此代码示例旨在说明特定技术,而不是其他任何内容. (10认同)
  • @FrankPuffer:我认为很明显这不应该是一个真正的功能.你断言功能"做了太多事情"基本上是没有根据的 - 你唯一的信息是功能名称,这显然是明显的!它可能也是`foo()`.这种切向评论是我对Stack Overflow感到沮丧的最大原因. (9认同)
  • @Galik使用这种模式,程序员必须更加睡着才能弄错,因为他们需要按名称引用字段.(直到他们忘记了为什么他们这样做并且认为通过支持的初始列表很聪明,以原始问题结束+新的无意义填充[编辑:nate,我们已经再次完成了]) (6认同)
  • @Galik即`allocate_things({1,10,93});` (4认同)
  • @FrankPuffer:如果你认为有一个真正的,善意的问题没有得到解决,那就把这个评论放在这个问题上,或者如果它足够重要的话就写下你自己的答案.执行"太多事情"的函数问题与所提出的实际问题完全无关. (4认同)
  • 我并不完全相信.这不就是把问题转移到其他地方吗?你不能不小心将成员变量设置为与参数变量一样错误吗? (3认同)
  • 我想指出的是,你的答案确实提供了解决问题的解决方法,但实际问题在于其他地方:应该避免使用具有三个或更多相同类型参数的函数.不仅因为界面难以正确使用,而且因为该功能很可能会做太多事情并且应该被拆分. (2认同)
  • 如果你确实使用了一个结构,你可能会发现人们使用隐式初始化语法`allocate_things({1,10,93})`,让你回到你开始的地方,但现在有一对额外的视觉噪音大括号.您可以通过指定构造函数来防止这种情况,但您仍然在为用户提供更多的生活. (2认同)

Nir*_*man 30

到目前为止,有两个好的答案,还有一个:另一种方法是尝试尽可能利用类型系统,并创建强大的typedef.例如,使用boost strong typedef(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);
Run Code Online (Sandbox Code Playgroud)

使用错误顺序的参数调用func现在将是编译错误.

关于此的几点说明.首先,boost的强大typedef在其方法上相当过时; 你可以使用可变参数CRTP做更好的事情并完全避免使用宏.其次,显然这会引入一些开销,因为您经常需要显式转换.所以一般来说你不想过度使用它.对于在您的图书馆中反复出现的事情,这真的很不错.对于那些作为一次性出现的东西来说不太好.因此,举例来说,如果你正在编写一个GPS库,那么你应该有一个强大的双模式,以米为单位的距离,一个强大的int64 typedef,用于超过纪元的时间,以纳秒为单位,依此类推.

  • 特别是对于整数,scoped enum是一个不错的选择. (2认同)
  • 你可以得到一个看起来像`allocate_things(40_buffers,22_pages,80 ......)的调用,如果你没有将值放在正确的位置,它会给你一个编译器错误. (2认同)

ser*_*gej 30

如果您有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);
}
Run Code Online (Sandbox Code Playgroud)

  • ......*哦哇*.+1; 这非常有趣.但我不知道我是否想要找到我需要它的场景...... ;-) (4认同)
  • @Joker_vD:用户定义的文字后缀是另一种方式._don't_以`_`开头的后缀是保留的.(C++11§17.6.4.3.5;没有以后版本的部分.) (3认同)

chu*_*ica 9

(注意:帖子最初被标记为'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});
Run Code Online (Sandbox Code Playgroud)

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

allocate_things(struct things *);

// Use a compound literal
allocate_things(&((struct things){.default_value=80,.num_buffers=40,.pages_per_buffer=22}));
Run Code Online (Sandbox Code Playgroud)


Fra*_*fer 7

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

在您的例子中,你可以像独立的功能set_num_buffers(int num_buffers),set_pages_per_buffer(int pages_per_buffer)等等.

您可能已经注意到自己allocate_things并不是一个好名字,因为它没有表达该功能实际上在做什么.特别是我不希望它设置默认值.

  • 为了使`set_XXX`影响未来的`allocate_things`调用,必须将参数存储在某处. (4认同)
  • 并分担责任. (3认同)
  • 并且不要使用魔术数字,像你这样的硬编码参数通常会导致比它的价值更多的痛苦. (3认同)
  • 这会给系统带来不必要的(可能是全局的)状态 (2认同)

Wal*_*ter 7

为了完整起见,您可以在通话结束时使用命名参数.

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);
Run Code Online (Sandbox Code Playgroud)

但是,使用当前的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);
}
Run Code Online (Sandbox Code Playgroud)

  • 看起来像C++ 35,或者...... +1.很想看到一个最小的工作示例. (2认同)

Dan*_*nes 6

你真的要尝试QA任意整数的所有组合吗?抛出所有负值/零值等检查?

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

allocate_things(MINIMUM_BUFFER_CONFIGURATION, LARGE_BUFFER_SIZE, 42);
Run Code Online (Sandbox Code Playgroud)

那么你只需要测试有限数量的组合,你将获得100%的覆盖率.5年后从事代码工作的人只需知道他们想要实现的目标,而不必猜测他们可能需要的数字或实际在现场测试的数值.

它确实使代码稍微难以扩展,但听起来这些参数适用于低级性能调优,因此不应将这些值视为廉价/微不足道/不需要彻底测试.来自allocate_something(25,25,25)的更改的代码审查;

...至

allocate_something(30,80,42);

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