【问题标题】:Default (Optional) Arguments as references c++默认(可选)参数作为引用 c++
【发布时间】:2019-12-12 07:04:42
【问题描述】:

是否有可能(不使用 boost)在 c++ 中具有如下功能:

void foo(int x, classX &obj = *(new classX()))

classX 在我的代码库中被多次使用,并且有许多具有相似签名的此类函数(即使用此类对象作为默认参数)。是否有可能在没有重载调用的情况下实现这一点?

【问题讨论】:

  • 如果引用是const,那么你可以这样做:void foo(int x, const classX &obj = classX()),否则使用重载更简洁:void foo(int x){ classX obj; foo(x, obj); } void foo(int x, classX &obj) {...}
  • void foo(int x, const classX &obj = {});
  • hmm .. 这些引用中的每一个都应该引用同一个对象吗?
  • 为什么要默认使用运算符new?如果foo() 忘记/未能使用相应的运算符delete,这是导致内存泄漏的一种非常有效的方法?
  • 为什么不直接使用普通指针并在函数中取消引用它们呢?不过,reference wrappers 可能会有所帮助。

标签: c++ overloading pass-by-reference default-arguments


【解决方案1】:

您提供的代码确实可以编译和“工作”,但我强烈建议不要这样做。

函数返回void,这意味着被引用或分配的对象(或其假定的所有者)不会离开函数。因此,如果分配,它必须被销毁(否则,这是其他人的问题,在该函数之外)。

但是,这甚至是不可能的,没有人拥有该对象,或者拥有指向它的指针!因此,您不仅存在 可能 内存泄漏,而且存在 保证 内存泄漏(如果没有传递对象),除非您添加另一个丑陋的 hack 派生来自引用的指针仅用于销毁对象。这很不愉快。

另外,即使您完成了这项工作(以无泄漏的方式),对于每个函数调用,您都有无用的对象分配和销毁。虽然不应该过早地进行优化,但同样也不应该过早地感到悲观,通过添加不仅不必要而且实际上会降低代码质量的常规分配和释放。

更好的是:

//namespace whatever {
classX dummy;
//}
#include <memory>

void foo(int x, classX &obj = dummy)
{
    if(std::addressof(obj) != std::addressof(dummy))
    { /* do something using object */ }
    else
    { /* no object supplied */ }
}

是的,这是一个用于良好事业的全局变量。如果这让您感觉更好,您可以将全局设置为单例,或者将其设置为静态类成员,都一样。无论哪种方式,您都只有一个对象,没有分配,没有泄漏,如果您愿意,您仍然可以将对象传递给函数。而且,您可以区分这两种情况。

【讨论】:

  • 然而,除非引用是 const,否则函数是不可重入的。
  • @JanHudec:怎么会这样?虚拟对象或其地址在启动时初始化后几乎没有变化。你几乎不会用它做任何事情,但比较它是否是假人。做任何事情都没有意义。顺便说一句,我建议将引用设为const(以传达这一点),但看到在对象提供的情况下,一个可能实际上想要修改传递的对象,即可能行不通。
  • 唯一有意义的是不区分参数是否是虚拟的!如果您需要区分,在大多数情况下,使用两个重载代码会更简单、更清晰。因此,显而易见的假设是始终使用该参数,并且由于它不是 const,因此已修改。
  • @JanHudec:问题是,OP 明确不希望过载,所以这是不可能的。我们知道的太少,无法确定,但是......您甚至可以可能同时修改虚拟对象,因为您这样做破坏它的事实可能无关紧要。是的,您可以构建一个不是这种情况的示例(我知道)。因此,由于没有过载的可能性,我更喜欢区分“提供对象”和“未提供对象”的情况。目前还不清楚我是否可以有意地修改默认构造的对象(无需先做一些额外的事情)。
  • 如果它只是一个可选的按引用返回,那没关系。但如果对象有一些在进程中调用的有用方法,它可能会调用。
【解决方案2】:

可以使用全局共享实例作为默认参数。

extern inline classX globalClassX;
void foo(int x, classX &obj = globalClassX);

应该已经完成​​了。

但是,我很不确定static initialization order fiasco 可能会干扰。

这可以使用 Meyers Singleton 方法解决:

classX& getGlobalClassX()
{
  static ClassX classX;
  return classX;
}

void foo(int x, classX &obj = getGlobalClassX);

用于演示的 MCVE:

#include <cassert>
#include <iostream>

struct Object {
  inline static unsigned idGen = 1;
  unsigned id;
  const std::string name;
  explicit Object(const std::string &name = std::string()): id(idGen++), name(name) { }
};

Object& getGlobalObj()
{
  static Object objGlobal("global");
  return objGlobal;
}

void doSomething(int x, Object &obj = getGlobalObj());

#define PRINT_AND_DO(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

int main()
{
  PRINT_AND_DO(doSomething(0));
  PRINT_AND_DO(Object obj("local"));
  PRINT_AND_DO(doSomething(1, obj));
  PRINT_AND_DO(doSomething(2));
}

void doSomething(int x, Object &obj)
{
  std::cout << "doSomething(x: " << x << ", obj: Object("
    << obj.id << ", '" << obj.name << "'))\n";
}

输出:

doSomething(0);
doSomething(x: 0, obj: Object(1, 'global'))
Object obj("local");
doSomething(1, obj);
doSomething(x: 1, obj: Object(2, 'local'))
doSomething(2);
doSomething(x: 2, obj: Object(1, 'global'))

Live Demo on coliru


我在写作时参考的另一个问答:

SO: Are static data members safe as C++ default arguments?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-12
    • 2019-05-19
    • 1970-01-01
    • 2015-07-29
    • 1970-01-01
    • 2011-02-13
    • 2011-07-04
    相关资源
    最近更新 更多