📜  C++ 中不同引用的重载

📅  最后修改于: 2022-05-13 01:54:54.721000             🧑  作者: Mango

C++ 中不同引用的重载

本文重点介绍引用的函数/方法重载,以及可以传递的参数类型。

先决条件:

  • l 值参考。
  • r 值参考。
  • 移动语义——std::move()。

概述:
l 值是指标识对象的内存位置。 r-value 是指存储在内存中某个地址的数据值。 C++ 中的引用只不过是已经存在的变量的替代品。它们是在变量名之前使用“&”声明的。自现代 C++ 兴起(即自 C++11 起)以来,已添加右值引用。
因此,现在有三个 Call-By-Reference 选项 -

  1. 通过指针。
  2. 通过使用左值(普通)引用。
  3. 通过使用右值引用(在 C++11 中首次引入)。

此处仅显示所有可能的左值和右值引用重载。

笔记:
为方便起见,std:: 字符串在以下示例中用作参数。也可以使用任何其他类型的参数(包括用户定义的类型)。

采用左值引用和右值引用的函数重载-

  1. 非常量左值引用。
  2. 常量左值引用。
  3. 非常量右值引用。
  4. 常量右值引用。

非常量左值引用

在这种情况下,函数接受非常量左值引用作为参数,这意味着可以修改提供的参数。

句法:

  • foo()函数接受一个非常量左值引用作为参数,这意味着可以修改(读/写)提供的参数。
  • 可以根据函数签名传递的变量/对象的类型(请参阅下面的程序)-
    1. 只有一个命名的可修改对象。 (以下程序中的案例 1)。

下面是实现上述方法的 C++ 程序——

C++14
// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include 
  
// for std::string
#include 
  
// for EXIT_SUCCESS
#include 
  
// for std::move()
#include 
  
// Declaration
  
// A foo() function takes the
// argument of non-const lvalue
// reference
void foo(std::string& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    // non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
    foo(namedNonConstObj);
  
    // Case 2 - A named const object
    // const object
    const std::string namedConstObject{
        "This is named const object"
    };
  
    // Error
    // foo(namedConstObject);
  
    // Case 3 - A unnamed temporary object
    // Error
    // foo(std::string("This is unnamed
    // temporary object"));
  
    // Case 4 - using std::move() for named
    // non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
  
    // Error
    // foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for named const object
    const std::string namedConstObjectWithMove{
        "This is named const object - using std::move()"
    };
  
    // foo(std::move(namedConstObjectWithMove));
    // Error
  
    /* Case 6 - using std::move() for unnamed 
  // temporary object */
    // foo(std::move(std::string("This is
    // unnamed temporary object - using
    // std::move()")));
    // Error
    return EXIT_SUCCESS;
}
  
// Definition
void foo(std::string& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}


C++14
// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include 
  
// for std::string
#include 
  
// for EXIT_SUCCESS
#include 
  
// for std::move()
#include 
  
// Declaration
  
// A foo() function takes the
// argument of const lvalue reference
void foo(const std::string& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // namedNonConstObj will be treated
    // as constant
    foo(namedNonConstObj);
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
    foo(namedConstObject);
  
    // Case 3 - A unnamed temporary
    // object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
    foo(std::move(namedConstObjWithMove));
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    foo(std::move(std::string(
        "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(const std::string& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}


C++14
// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include 
  
// for std::string
#include 
  
// for EXIT_SUCCESS
#include 
  
// for std::move()
#include 
  
// Declaration
  
// A foo() function takes the argument
// of non-const rvalue reference
void foo(std::string&& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // foo(namedNonConstObj);
    // Error
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
  
    // foo(namedConstObject);
    // Error
  
    // Case 3 - A unnamed temporary object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
  
    // foo(std::move(namedConstObjWithMove));
    // Error
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    // Use of std::move() with temporary
    // objects is not recommended,
    // if the function with rvalue reference
    // as an argument exist.
    foo(std::move(
        std::string(
            "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(std::string&& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}


C++14
// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include 
  
// for std::string
#include 
  
// for EXIT_SUCCESS
#include 
  
// for std::move()
#include 
  
// Declaration
  
// A foo() function takes the
// argument of const rvalue reference
void foo(const std::string&& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // Error
    // foo(namedNonConstObj);
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
  
    // Error
    // foo(namedConstObject);
  
    // Case 3 - A unnamed temporary object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
    foo(std::move(namedConstObjWithMove));
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    // Use of std::move() with temporary
    // objects is not recommended,
    // if the function with rvalue
    // reference as an argument exist.
    foo(std::move(std::string(
        "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(const std::string&& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}


输出

解释:
情况 1:非常量对象引用可以指向非常量对象。
案例 2:非 const对象引用不能指向const 对象。
案例 3:它将尝试隐式地利用移动语义,将左值引用更改为右值引用,尽管事实上没有函数接受右值引用作为输入。因此,复制语义将用作移动语义的后备。但是对于复制语义,需要一个接受const 左值引用作为参数的函数,在这种情况下不存在。
案例 4 到 6:案例 4、5 和 6 与案例 3 相同,不同之处在于我们明确指定调用接受右值引用的函数/方法 通过用 std::move() 标记对象作为参数。

常量左值参考

在这种情况下,函数接受一个 const 左值参数,这意味着无法修改,只能读取提供的参数。

  • 此 foo()函数采用 const 左值引用参数,这意味着只能读取提供的参数。
  • 可以根据函数签名传递的变量/对象的类型(请参阅下面的程序) -
    1. 一个可修改的命名对象。 (以下程序中的案例 1)。
    2. 一个 const 命名对象。 (以下程序中的案例 2)。
    3. 一个(未命名的)临时对象。 (以下程序中的案例 3)。
    4. 用 std::move() 标记的对象。 (以下程序中的案例 4 到 6)。

笔记:
当函数或方法被右值引用参数重载时,不需要用 std::move() 标记临时对象。

下面是实现上述方法的 C++ 程序——

C++14

// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include 
  
// for std::string
#include 
  
// for EXIT_SUCCESS
#include 
  
// for std::move()
#include 
  
// Declaration
  
// A foo() function takes the
// argument of const lvalue reference
void foo(const std::string& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // namedNonConstObj will be treated
    // as constant
    foo(namedNonConstObj);
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
    foo(namedConstObject);
  
    // Case 3 - A unnamed temporary
    // object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
    foo(std::move(namedConstObjWithMove));
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    foo(std::move(std::string(
        "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(const std::string& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}
输出

解释:
情况 1:常量对象引用可以指向非常量对象。
情况2 :常量对象引用可以指向常量对象。
案例 3:它将尝试隐式地利用移动语义,将左值引用更改为右值引用,尽管事实上没有函数接受右值引用作为输入。因此,复制语义将用作移动语义的后备。对于复制语义,需要一个接受 const 左值引用作为参数的函数,在这种情况下存在。这就是函数调用编译成功的原因。
案例 4 到 6:案例 4、5 和 6 与案例 3 相同,除了我们明确指定调用一个函数/方法,该函数/方法通过使用 std::move() 标记对象来接受右值引用作为参数.

非常量右值引用

在这种情况下,函数接受非常量右值引用,这意味着可以修改传递参数。

  • 此 foo()函数接受非 const 右值引用作为输入参数,这意味着您可以修改(读/写)传递的参数。然而,右值引用被用作与移动语义相关联的来窃取资源。
  • 可以根据函数签名传递的变量/对象的类型(请参阅下面的程序)-
    1. 没有名称的临时对象,即未命名对象。 (以下程序中的案例 3)。
    2. 用 std::move() 标记的非常量对象。 (以下程序中的案例 4)。

下面是实现上述方法的 C++ 程序——

C++14

// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include 
  
// for std::string
#include 
  
// for EXIT_SUCCESS
#include 
  
// for std::move()
#include 
  
// Declaration
  
// A foo() function takes the argument
// of non-const rvalue reference
void foo(std::string&& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // foo(namedNonConstObj);
    // Error
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
  
    // foo(namedConstObject);
    // Error
  
    // Case 3 - A unnamed temporary object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
  
    // foo(std::move(namedConstObjWithMove));
    // Error
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    // Use of std::move() with temporary
    // objects is not recommended,
    // if the function with rvalue reference
    // as an argument exist.
    foo(std::move(
        std::string(
            "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(std::string&& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}
输出

解释:
情况 1:非常量左值对象不能传递给将非常量右值引用作为参数的函数(除非对象用 std::move() 标记)。
情况 2 和 5:不能将const 左值对象传递给将非 const 右值引用作为参数的函数(即使在对象被 std::move() 标记后)。
情况 3:编译器将指示应使用将右值引用作为参数的函数。在我们的示例中,它确实存在。情况 4:非 const 左值对象可以传递给一个函数,该函数仅当一个对象用 std::move() 标记时才将非 const 右值引用作为参数。
案例 6:与案例 3 类似。如果存在将右值引用作为参数的函数,则不建议将 std::move() 与临时对象一起使用。

常量右值参考:
在这种情况下,函数采用 const 右值引用,这意味着只能读取提供的参数。

  • 此 foo()函数采用const rvalue 引用参数,这意味着您只能读取传递的参数。
  • 可以根据函数签名传递的变量/对象的类型(请参阅下面的程序) -
    1. 没有名称的临时对象。 (以下程序中的案例 3)。
    2. 用 std::move() 标记的 const 或非 const 对象。 下面程序中的案例 4 和 5)。
  • 因为右值引用旨在窃取资源,所以 const 带有对对象的右值引用的说明符相矛盾。窃取常量对象的资源也是没有意义的。

下面是实现上述方法的 C++ 程序——

C++14

// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include 
  
// for std::string
#include 
  
// for EXIT_SUCCESS
#include 
  
// for std::move()
#include 
  
// Declaration
  
// A foo() function takes the
// argument of const rvalue reference
void foo(const std::string&& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // Error
    // foo(namedNonConstObj);
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
  
    // Error
    // foo(namedConstObject);
  
    // Case 3 - A unnamed temporary object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
    foo(std::move(namedConstObjWithMove));
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    // Use of std::move() with temporary
    // objects is not recommended,
    // if the function with rvalue
    // reference as an argument exist.
    foo(std::move(std::string(
        "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(const std::string&& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}
输出

解释:
情况 1 和 2:不能将 const 或非 const 命名对象传递给将 const 右值引用作为参数的函数。
情况 3:也可以将临时对象传递给接受 const 右值引用的函数。
情况 4 和 5:仅当使用 std::move() 显式指示 const 或非 const 命名对象时,才能将它们提供给将 const 右值引用作为参数的函数。
情况 6:当函数被const或非 const右值引用参数重载时,不需要用std::move()标记临时对象。

概括:
可以通过引用传递给重载函数或方法的参数类型。

  • 在调用采用 const 或非 const 左值引用或右值引用的参数的函数/方法时不使用 std::move()

  • 在调用接受const或非 const左值引用和/或右值引用的参数的函数/方法时使用 std::move()