引用初始化表单

Reference initialization forms

本文关键字:表单 初始化 引用      更新时间:2023-10-16

所以我正在测试一些引用初始化形式,在这里描述。我想知道什么时候:

T & ref = { arg1, arg2, ... };

T && ref = { arg1, arg2, ... };

形式将永远使用以及确切用于什么。我想它用于用"initializer_list"初始化临时数组和构造函数,如下所示:

int main()
{

    struct _ab
    {
        _ab() {cout << "_ab()" << endl;}
        _ab(initializer_list<int> iArr) : a(*iArr.begin()), b(*iArr.end()) {cout << "_ab()" << endl;}
        ~_ab() {cout << "~_ab()" << endl;}
        int a, b;
    };
    const _ab & i = {1, 2};
    cout << i.a << endl;
    return 0;
}

在这种情况下,我尝试使用默认构造函数使用临时的"_ab"对象初始化 const 引用,如下所示:

int main()
    {

        struct _ab
        {
            _ab() {cout << "_ab()" << endl;}
            _ab(initializer_list<int> iArr) : a(*iArr.begin()), b(*iArr.end()) {cout << "_ab()" << endl;}
            ~_ab() {cout << "~_ab()" << endl;}
            int a, b;
        };
        const _ab & i(); // error 1
        cout << i.a << endl; // error 2
        return 0;
    }

但是此示例没有编译 2 个错误:

错误 1:"const main()::_ab& i()",使用本地类型"const "声明 main()::_ab',已使用但从未定义 [-fpermissive]|

错误 2:请求"i"中的成员"a",该成员属于非类类型 'const main()::_ab&()'|

你能告诉我上述 2 个结构到底是什么意思和用途吗?

编辑:我理解第二个例子的问题。它声明的是函数而不是变量。但是仍然有人可以解释为什么引用可以使用初始化列表进行初始化以及它用于什么?

const _ab & i();

该标准在 [dcl.init]/8 中的注释中对此进行了解释:

[ 注意:由于初始值设定项的语法不允许()

X a();

不是类 X 对象的声明,而是声明 一个不带参数并返回X的函数。

无论类型如何,这都适用,并且被称为最令人烦恼的解析。当然,i.a因此是畸形的。


但是仍然有人可以解释为什么参考文献有可能 使用初始化列表进行初始化,它用于什么?

[dcl.init]/3:

类型为 T 的对象或引用的列表初始化定义如下:

  • [..]

  • 否则,如果初始值设定项列表具有类型为 E 的单个元素,并且 T 不是引用类型或其引用类型为 与E相关的引用,对象或引用初始化自 该元素;如果需要缩小转换范围(见下文) 将元素转换为 T,程序格式不正确。

  • 否则,如果 T 是引用类型,则 T 引用的类型的临时 prvalue 将复制列表初始化或 直接列表初始化,具体取决于 的初始化类型 引用,并且引用绑定到该临时引用。
    [ 注意:像往常一样,如果 引用类型是对非 const 类型的左值引用。— 尾注 ]

最后一点指的是

int& i = {1};

格式不正确,因为我们必须将1绑定到非常量左值引用,这是不可能的。也不会

std::string& s = {20, '5'};

是有效的,因为我们必须用临时初始化一个非常量左值引用。
然后,该标准给出了示例:

struct S {
    S(std::initializer_list<double>); // #1
    S(const std::string&);            // #2
    // ...
};
const S& r1 = { 1, 2, 3.0 };    // OK: invoke #1
const S& r2 { "Spinach" };      // OK: invoke #2
S& r3 = { 1, 2, 3 };            // error: initializer is not an lvalue
const int& i1 = { 1 };          // OK
const int& i2 = { 1.1 };        // error: narrowing
const int (&iar)[2] = { 1, 2 }; // OK: iar is bound to temporary array

这对于函数调用也特别有用。

void f(std::vector<int> const&);
f( {first, last} );

const _ab & i();

上面的代码声明(并调用)一个函数,该函数返回对_ab结构的 const 引用 - 这是第一个错误。第二个错误是你尝试访问函数的成员变量 ( i ),这是不可能的。

要使用默认构造函数,您可以使用新的 {...} C++11 语法,它应该在您的示例中工作

const _ab & i {}; // probably will work fine

但是省略大括号会导致错误 - 这个不起作用:

const _ab & i;  // error

这也应该可以正常工作:

const _ab & i = {};

编辑:

仅当"匿名"对象是

const引用时,才可以使用"匿名"对象初始化引用(如所有示例中)。它在语言中是允许的,我不确定是否有任何更深层次的原因以及为什么不应该允许它。因此,您实际上不会使用初始值设定项列表(或其他任何内容)初始化引用 - 这些用于初始化匿名对象,并且此匿名对象用作 const 引用的"目标"。如果你将构造函数声明为explicit,你需要指定类型,所以也许在这种情况下,它不会像实际使用的初始值设定项那样令人困惑:

const _ab & i = _ab{/*...*/};

这相当于:

const _ab anonymousObject = _ab{/*...*/};
const _ab & i = anonymousObject;

您所看到的类似于允许将右值绑定到常量引用的情况,例如

std::string str() { return "blah"; }
const std::string& s = str();

函数返回的 prvalue 绑定到引用,其生存期将延长,直到引用的生存期结束。

执行相同规则的另一种方法是直接创建一个临时对象:

const std::string& s = std::string();

这将创建一个绑定到引用的临时(将其生存期延长到引用的生存期)。

const _ab& i = {1, 2} 的情况非常相似,但依赖于从大括号 init-list 构造的 _ab 类型的隐式临时,即它类似于这样:

const _ab& i = _ab{1, 2};

(不同之处在于,如果_ab的构造函数initializer_list explicit则第一种形式不起作用,而第二种形式即使对于显式构造函数也有效......但是将初始值设定项列表构造函数标记为explicit是一个坏主意,应该避免,因为它会使初始化对象变得困难和混乱)。

此语法不一定与上面写的完全有用,因为您通常只编写const _ab{1, 2};而不是使用引用。但是当你调用一个接受 const 引用的函数时,能够使用大括号的 init-list 初始化参数很有用,例如,一个接受 std::pair<int, const char*> 的函数可以调用为 func({1, "two"}) ,这比 C++03 等价物更方便:func(std::make_pair(1, "two")) 。为了使其正常工作,引用的列表初始化必须能够导致隐式创建临时,从而导致您询问的const _ab& = {1, 2}形式。