声明一个引用并稍后初始化?

100

我有一个指向类MyObject的引用,但具体的对象取决于一个条件。我想做这样的事情:

MyObject& ref; 
if([condition]) 
  ref = MyObject([something]);
else 
  ref = MyObject([something else]);

我现在无法做到这一点,因为编译器不允许我声明但不初始化引用。在这种情况下,我该怎么做才能实现我的目标呢?


3
这是来自一段代码的评论,原文可能是这样的:"Would that be an initialization from a temporary? That won't work even without the condition: MyObject& ref = MyObject([something]);, because you cannot bind a temporary to a non-const lvalue reference."意思是:这是从一份临时代码中提取出来的注释。即使没有条件限制,如下初始化方式也行不通:MyObject& ref = MyObject([something]);,因为你不能将临时对象绑定到非 const 左值引用上。 - GManNickG
@GManNickG:这也适用于Zaffy和suszterpatt的回答吗? - qPCR4vir
@qPCR4vir:是的。问题仍然存在,以某种方式存在,只是不是直接的。 - GManNickG
1
如果您正在寻找实际解决方案,请向下滚动至 https://dev59.com/X2Up5IYBdhLWcg3w_rkl#50909452。 - hellow
本问题和下面 @Latawiec 的回答涉及到立即调用的 lambda 表达式,非常有用于基于复杂表达式的引用和常量的初始化。 - Hari
13个回答

66
你需要初始化它。但是如果你想有条件地初始化它,你可以这样做:

你需要初始化它。但是如果你想有条件地初始化它,你可以这样做:

MyObject& ref = (condition) ? MyObject([something]) : MyObject([something else]);

13
我不能这样做,因为我在这些条件中实际上会进行一些操作。我非常困惑。对我来说,这似乎是一个非常常见的情况,为什么不允许呢?难道我的编程风格与C++格格不入吗? - user1861088
4
“我不能这么做,因为我实际上是在那些条件下执行任务。” 那么为什么不在问题中展示你想要做的事情呢? - GManNickG
1
我的意思是在每个条件语句中执行多行代码,而不仅仅调用返回值的一个函数。所以我不能使用?:对吧? - user1861088
1
@user1861088 你能否初始化引用,然后再测试条件并执行多行代码?一旦创建了引用,就无法更改它们。引用永远不可能为null。这两个限制防止你在问题中所发布的操作。 - iheanyi
9
你可以考虑使用 lambda。 - sergiol
显示剩余2条评论

29

据我所知,这不能使用引用完成。你需要使用指针:

MyClass *ptr;

if (condition)
    ptr = &object;
else
    ptr = &other_object;

指针类似于引用,只是别忘了使用->来访问成员。


8
如果您想将指针转换为引用,可以使用MyClass &ref = *ptr这样的语句来声明一个引用。请注意,此语句仅限于将指针转换为引用,并不会改变指针本身的类型或值。 - poizan42
不会像引用一样运作,因为它将被放置在堆上并且需要手动删除。 - Josu Goñi
2
@JosuGoñi 两者都不是真的。只有在我通过 new 分配内存时才会如此。ptr 指向在堆栈上分配的对象。 - David G
@0x499602D2 除非他可以使用移动构造函数,否则他必须使用 new。 - Josu Goñi
1
@JosuGoñi 给指针赋值不会导致复制或移动。虽然我承认我的答案不是最好的,但当时我几乎什么都不知道。今天我会建议将ptr变成值类型并分配给值。如果它不能默认初始化,那么我会建议使用std::unique_ptr,并在每个条件中使用std::make_unique<MyClass>(...)进行分配。 - David G
显示剩余3条评论

22

我喜欢做的是立即执行的lambda表达式。

假设我们想要一个 const std::string& 类型的变量,从映射中获取一个键对应的值 - 如果映射中不包含给定的键,则会抛出异常。

int main()
{
  std::map<std::string, std::string> myMap = {{"key", "value"}};

  const std::string& strRef = [&]()->const std::string& {
    try {
      return myMap.at("key"); // map::at might throw out_of_range
    }
    catch (...) {
      // handle it somehow and/or rethrow.
    }
  }(); // <- here we immediately call just created lambda.
}

自C++17起,您还可以使用std::invoke()使其更易读。

int main()
{
  std::map<std::string, std::string> myMap = {{"key", "value"}};

  const std::string& strRef = std::invoke([&]()->const std::string& {
    try {
      return myMap.at("key"); // map::at might throw out_of_range
    }
    catch (...) {
      // handle it somehow and/or rethrow.
    }
  });
}

4
是的,如果人们能够回答问题而不是建议不应该问这个问题就好了。这可能是最干净的即时方法,实际上可以解决原帖的问题。点赞。大家应该更经常向下滚动... 哈哈 - violet313
非常有用的答案。欲了解更多类似用法,请参见此处:https://www.cppstories.com/2016/11/iife-for-complex-initialization/ - Hari

18
你无法这样做。引用必须绑定到某个东西,你可能不喜欢它,但它可以防止一整类错误,因为如果你有一个引用,你总可以假设它被绑定到某个东西,而指针则可能为空。
你的示例代码本来就行不通,因为你试图将非const引用绑定到临时对象,这是无效的。
你为什么需要它是一个引用呢?一个解决方案是确保你的类型具有廉价的默认构造函数并且可以高效移动,然后只需执行以下操作:
MyObject obj; 
if([condition]) 
  obj = MyObject([something]) 
else 
  obj = MyObject([something else]);

否则,您将不得不将条件代码放在一个或多个函数中,其中:
const MyObject& ref = createObject([condition]);

或者

const MyObject& ref = [condition] ? doSomething() : doSomethingElse();

请注意,这两个版本都使用了一个const引用,它可以绑定到一个临时对象,如果对象必须是非常量,则不要再尝试使用引用。
MyObject obj = createObject([condition]);

这个方法可能和你之前尝试的一样有效,多亏了返回值优化


这应该是首选答案! - Ashley Duncan

13
在C++中,你不能声明一个未初始化的引用。你必须对它进行初始化。

1
我明白了。那么有什么解决方法吗?基本上,我需要在比条件更大的范围内引用该参考文献。 - user1861088
在这种情况下,要么1.重新设计你的代码(首选),要么2.使用指针(不是首选)。 - user529758
1
  1. 不要使用引用,直接使用对象。
- Jonathan Wakely
@user529758,对于在类内部声明的引用变量是真的吗?class test{ test(param o ):ref(o){}myobj& ref;} - RaGa__M

12

简短回答:你不需要这样做。

稍微详细一些的回答:可以尝试像这样做:

MyObject& getObject()
{
    if([condition]) 
        return [something] 
    else 
        return [something else];
}

MyObject& ref = getObject();

当然,通常适用于参考文献的免责声明也适用于此。


1
是的,我也考虑过这个问题,但是...为了实现一个看似简单的目标,我必须做这么多奇怪的事情,感觉很奇怪 :( - user1861088
getObject() 为什么会返回一个引用?它所指的是什么? - Jonathan Wakely
乔纳森:那属于“通常的免责声明”部分。 ;) - suszterpatt
1
@user1861088,你看似简单的目标是不被标准所允许的。它与引用的定义相冲突。这就好比你问为什么不能把一块石头扔到空中而它不会掉落到地面上。那很简单,对吧——只需要扔石头,然后它就会停留在原地。 - iheanyi

4
MyClass *ptr;

if (condition)
    ptr = &object;
else
    ptr = &other_object;

MyClass &ref = *ptr;

4
回答时,请考虑对您编写的代码进行解释,并使此回答对非常古老的问题没有任何新内容的情况下更加易懂。以下是需要翻译的内容:"When answering, consider writing an explanation of the code you wrote, and this answer adds absolutely nothing new to the very old question." - Ajean
1
这看起来非常非常类似于这个答案中的代码。 - Artjom B.
虽然缺乏任何解释是不好的,但这仍然是唯一真正的答案,不会引入任何额外的东西或限制您只能在三元运算符中编写什么。 - poizan42
1
@ArtjomB。那个答案没有在最后提出MyClass &ref = *ptr;,这是避免后续进一步解引用的关键行,更多注释请参见:https://dev59.com/E2ox5IYBdhLWcg3wcj94#62793754 - Ciro Santilli OurBigBook.com

2

可以使用静态的虚拟对象或者带有引用包装器的std::optional作为占位符。

static X placeHolder_;
std::reference_wrapper<X> ref = placeHolder_;
   


std::optional<std::reference_wrapper<X>> ref ;    

0
我有一个类似的问题,但解决方法不太聪明。 问题: 我需要声明一个变量,它的类型由"data"元素决定;该变量将在第一个 if 条件之外使用,因此需要在第一个 if 条件之外初始化。
Ivar& ref; // invalid, need init
if([condition]){ 
  ref = data["info"];
  if (ref.is_dict()) {
     ...
  }
}
string x = ref["aa"]; 


  

在条件语句中使用临时变量

Ivar& ref = dict(); // Ivar and dict are faked; Ivar is interface variable 
if([condition]){ 
  Ivar& ref_tmp = data["info"];
  if (ref_tmp.is_dict()) { // if the type of temp variable is correct
     ref = ref_tmp  // assign ref_tmp to ref
     ...
  }
}
string x = ref["aa"]; 

0
通常我会使用以下代码(C++11或更高版本):
std::shared_ptr<ObjType> pObj;
if(condition)
    pObj = std::make_shared<ObjType>(args_to_constructor_1);
else
    pObj = std::make_shared<ObjType>(args_to_constructor_2);

它是干净的,并且允许使用对象定义与(可能不同的)构造,这是您不能直接使用指针进行的临时对象的编译器将抱怨的事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接