【问题标题】:Avoid creating temporary vars for params which are not defaulted?避免为未默认的参数创建临时变量?
【发布时间】:2017-03-22 16:18:39
【问题描述】:

这听起来可能是个疯狂的问题。

在我的 C++ 代码中,我创建了一个这样的方法

void Func(int & param_1, bool & param_2, float & param_3, double & param_4) {
   //some logic
}

所以我调用main 中的方法,如下所示:

int i_val;
bool b_val;
float f_val;
double d_val;

//invoke Func here
Func(i_val, b_val, f_val, d_val);

问题:有没有办法避免创建临时变量i_val, b_val, f_val 等等?并在调用本身的行中创建它们?

我只对创建 i_valb_val 并获取它们的值感兴趣。 f_vald_val 对我来说是不必要的,但在其他一些与我无关的电话中是必需的。有没有办法避免创建临时变量只是将每个参数传递给调用?

我知道我们可以将最后 2 个参数设置为默认参数,但是使用默认参数会使函数参数忽略调用者。有没有办法不将最后 2 个参数设为默认值?在调用方法的那一刻动态创建 float 和 double 变量

我知道我会收到关于您为什么不想使用默认参数而只是检查是否有可能的交叉问题:)

【问题讨论】:

  • 使用函数重载
  • 除非有很好的理由不这样做,否则我会返回一个结构。然后你会得到你想要的。顺便说一句,通过这样做,您可以声明结果变量 const。

标签: c++ c++14 c++17


【解决方案1】:

当你有 四个 参数时,就像你在这里做的那样:

void Func(int & param_1, bool & param_2, float & param_3, double & param_4);

这表明你真的想返回一个有 4 个成员的对象,比如:

std::tuple<int, bool, float, double> Func();

或:

struct X {
    int some;
    bool meaningful;
    float names;
    double here;
};

X Func();

这样,你可以写:

auto res = Func();

然后只使用您想要的字段。


在 C++17 中,具有结构化绑定,可以是:

auto [ival, bval, _1, _2] = Func();

没有真正明确的方式来表达您不关心第 3 和第 4 成员的概念,但这还不错。

【讨论】:

  • 这是 明智的建议。我还鼓励在结果变量上使用 const,除非有令人信服的理由不这样做。
  • @gd1 不这样做的令人信服的理由是,您可以从结果中转移。
  • 如果你不需要(而且,好吧,如果结构是由这些东西组成的......)你宁愿少一个你可以搞砸的变量,编译器会帮助你阻止你从这样做。
  • 我无法使用“operator tie”技术。伤心。
【解决方案2】:

C++ 经验法则: 当你做一些强制性的事情时的参考 当你做一些可选的事情时的指针。当然,该函数中的代码需要考虑可以传递的nullptr。

做起来

void Func(int* pParam_1, bool* pParam_2, float* pParam_3, double* pParam_4) {
// some logic
}

【讨论】:

  • 我也不明白反对票。让我们把它擦掉。这是一种老式的方式,但这并不意味着它是一种糟糕的方式。
  • 我没有投反对票,但我认为在 C++ 中将四个指针作为可能可选输出(非常量)参数的函数是一场恐怖表演。
  • @gd1:还有其他方法。但是这个很简单,取决于上下文和要求可以非常合适。话虽如此,如果您说的是通用设计,那么无论使用哪种语言,可选 io 参数的函数都很奇怪...
  • @gd1 我完全同意,我自己也不会这样做,但是从原始问题的那一点上下文来看,这是一个有效的选择。
  • OP 可能可以随心所欲地设计功能,因此建议一种在大多数情况下都有效且看起来不那么过时且容易出错的设计是有意义的。除非有一个令人信服的案例可以遵循这条路线(我非常怀疑这种情况下是否存在,即使我无法查看整个程序)......
【解决方案3】:

使用非const 引用,例如您拥有的,不。您可以考虑函数重载,返回包含这些值的 struct,甚至从 C++11 开始返回 std::tuple。 (请注意,std::tuple 在 C++14 中得到了重大升级。)

如果您引用 const* 并依赖返回的结果值,事情会变得更好。然后,您可以在调用站点将 匿名临时对象 传递给您的函数:

/*something*/ Func(const int& param_1, const bool& param_2, /*etc*/) {
}

并使用Func(1, true) 等调用。使用const 引用,您甚至可以提供默认值:

/*something*/ Func(const int& param_1 = 1, const bool& params_2 = true, /*etc*/){

* 一些编译器允许将匿名临时绑定到非const 引用作为扩展/无意的错误。但如果我是你,我不会依赖它。

【讨论】:

  • 除非有非常有力的理由(我严重怀疑)反对它,否则我会返回一个结构。
  • @gd1:我同意,我个人不喜欢元组,std::pair 对我来说是边界。
  • @Bathsheba pairtuple 好多少?我会反过来订购它们; tuple 至少在通用代码中用于捆绑数据以供以后使用;同时pair 到处都是代码气味。
  • 尽管它们在许多情况下都是有用的工具,尤其是带有模板的std::tuple,但我认为任何面向客户端的界面都不应该既不接受也不返回std::pairstd::tuple。它们没有语义,它们只是“一些东西”。当std::tuple 被引入时,我在颤抖,因为我想“任何懒得写结构的人都会返回一个std::tuple 的未命名的'事物'”。我不用等太久。
【解决方案4】:

你可以,但你必须注意,虚拟临时人员会在完整表达式结束时死亡:

template <typename T>
T& stay(T&& x = T()) {
    return x;
}

void Func(int& param_1, bool& param_2, float& param_3, double& param_4) {
   //some logic
}

int main() {
    int arg_1; float arg_3;
    Func(arg_1, stay<bool>(), arg_3, stay(42.0));
}

stay 将右值转换为左值,因此与std::move 相反。

【讨论】:

  • 我不明白你为什么std::remove_reference_t;它的存在让我很困惑,我基本上回答了你的一个副本。在什么情况下需要remove_reference_t
  • @Yakk 你是对的,这里是多余的;附加的左值引用将折叠任何 ref-qualifiers T 。相应地进行了编辑。
【解决方案5】:

在调用方法的时候动态创建浮点和双变量

为此,请使用您想要的值创建一个匿名临时。然后将其强制转换,以便您可以绑定到左值引用。

template<class T>
T& as_lvalue(T&& t={}){return t;}

void Func(int& param_1, bool& param_2, float& param_3, double& param_4) {
}

int main() {
  int a1;
  float a3;
  Func(a1, as_lvalue(false), a3, as_lvalue(3.14));
}

as_lvalue 接受一个右值(或左值!)并将其转换为相同类型的左值。然后它可以绑定到一个左值引用,并在表达式的末尾被丢弃。

如果你不在乎它们的价值是什么,你甚至可以这样做:

int main() {
  int a1;
  float a3;
  Func(a1, as_lvalue<bool>(), a3, as_lvalue<double>());
}

请注意,使用引用参数作为纯参数会有点代码味道。使用时,参考参数应该是in-out参数;它们的进出价值应该是有用的。

同时传入和取出数据的参数也很危险,因为它使推理函数的行为变得更加困难。

一种非常干净的方法是获取并返回一个结构。

struct FuncArgs {
  int param_1;
  bool param_2;
  float param_3;
  double param_4;
};
FuncArgs Func(FuncArgs) {
  //some logic
}

人们可以这样称呼它:

Func({3, true, 42.f, 3.14})

并像这样获取返回数据:

auto r = Func({3, true, 42.f, 3.14})
int param1 = r.param1;
bool param2 = r.param2;

忽略返回的param3param4

在 C++17 中,他们甚至可以这样做:

auto[param1, param2, donotcare, alsodonotcare] = Func({3, true, 42.f, 3.14});

遗憾的是,无法完全跳过 donotcarealsodonotcare 字段。

但是,如果您的数据是纯结果,我们只需去除 FuncArgs 参数。

现在,修改每个调用Func 的位置可能很烦人;但是由于重载的奇迹,我们可以写:

inline FuncArgs Func() {
  FuncArgs args;
  Func(args.param1, args.param2, args.param3, args.param4);
  return args;
}

inline FuncArgs Func(FuncArgs args) {
  Func(args.param1, args.param2, args.param3, args.param4);
  return args;
}

两人幸福地生活在一起。无需触及现有代码;新代码是。

【讨论】:

  • 绑定到t 的默认临时文件的生命周期不会在at_lvalue 的末尾结束吗?
  • @yurikilochek 不,它在您调用as_lvalue 的“行尾”处结束。绑定到引用不会缩短它们的生命周期。 {} 存在于调用的位置,显式的false 也在那里。烦人的是,如果不使用static 变量,我无法弄清楚如何进行ignored_reference() 构造; operator T() 不能接受争论!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2020-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多