以下代码会导致未定义的行为:
class T
{
public:
const std::string& get() const { return s_; }
private:
std::string s_ { "test" };
}
void breaking()
{
const auto& str = T{}.get();
// do sth with "str" <-- UB
}
Run Code Online (Sandbox Code Playgroud)
(因为const&根据我的理解,生命周期延长不适用于此处)。
为了防止这种情况,一种解决方案可能是添加一个引用限定符get()以防止在 LValues 上调用它:
const std::string& get() const & { return s_; }
Run Code Online (Sandbox Code Playgroud)
但是,由于函数现在既是const又是&合格的,因此仍然可以调用get()RValues,因为它们可以分配给const&:
const auto& t = T{}; // OK
const auto& s1 = t.get(); // OK
const auto& s2 = …Run Code Online (Sandbox Code Playgroud) 我刚刚想到operator+&co可以this为rvalues 操作; 即给定一个类C,它可以这样做:
class C {
// ...
C operator-( const C& rhs ) const & {
C result = *this;
result -= rhs;
return result;
}
C&& operator-( const C& rhs ) && {
*this -= rhs;
return std::move( *this );
}
};
Run Code Online (Sandbox Code Playgroud)
这样可以通过简单地就地修改临时值来防止复制.
这会像我期望的那样表现吗?这是一个合理的优化还是编译器会创建同样快速的代码?
C++11 引入了引用限定成员函数以及完美转发的功能。但我们可以将它们混合在一起吗?
考虑这个(工作)示例:
struct bar{
std::string str;
void do_stuff() & {
/* version 1 */
}
void do_stuff() && {
/* version 2 */
}
};
struct foo{
bar data;
void do_stuff() & {
data.do_stuff();
}
void do_stuff() && {
std::move(data).do_stuff();
}
};
int main(){
foo f;
f.do_stuff() // calls version 1 of bar::do_stuff()
std::move(f).do_stuff() // calls version 2 of bar::do_stuff()
}
Run Code Online (Sandbox Code Playgroud)
在内部main(),第一个调用调用版本 1 或bar::do_stuff(),第二个调用调用版本 2 或bar::do_stuff()。中存在一些重复的代码foo::do_stuff()。如果 ref-qualifiers 用于除隐含的参数之外的参数*this,我们可以轻松地进行完美转发:
template …Run Code Online (Sandbox Code Playgroud) 为什么元素访问 STL 容器的成员函数,例如std::array::operator[]或std::vector::operator[]没有右值引用限定符重载?当然我可以做std::move(generate_vector()[10]),但我很好奇在标准化引用限定符时是否考虑添加右值引用限定符重载。
我认为std::array<T, N>和std::tuple<T, T, ..., T>真的是一回事,std::get后者的“元素访问函数(即)”对于 const 与非常量以及左值与右值的所有组合都被重载。为什么不是前者?
将右值引用限定的元素访问成员函数(返回右值引用)添加到我的自定义容器是个好主意吗?
对于理查德克里滕的评论。我认为这有时会很有用。
例如,您有一个函数返回在该函数内部构造的容器,但您可能只对该容器的第一个元素感兴趣。是的,这很愚蠢。在这种情况下,最好使用仅构造第一个元素的更简单的函数。但是如果这个功能不是你的,你就没有这样的选择。
或者,可能有更一般的例子。您有一个构造容器的函数,并且您希望处理该容器以获得另一个结果。例如,您可能希望执行std::reduce, 或std::unique_copy到那个容器。(似乎在执行期间禁止修改元素std::reduce,但让我们假设我们已经实现了允许修改的元素。)在这种情况下,可以使用std::make_move_iterator,但为什么不让容器本身返回移动迭代器呢?
事实上,当我将一些“视图”类实现到容器类时,我遇到了这个问题。可变视图(左值引用)、不可变视图(常量引用)和可移动视图(右值引用)似乎都需要,我必须确定从可移动视图类的元素访问成员函数返回什么:左值或右值引用? 我觉得有点奇怪,将右值引用返回到容器本身没有公开此类接口的元素。哪一个是正确的?
我试图找出为什么以下代码片段调用 LValue 强制转换运算符重载:
#include <iostream>
class Foo
{
public:
Foo(int i = 0) : i(i) {}
operator const int& () const &
{
std::cout << "lvalue\n";
return i;
}
operator int () const &&
{
std::cout << "rvalue\n";
return i;
}
int i = 0;
};
Foo Fool()
{
return Foo(5);
}
int main()
{
const int& i = Fool();
const int j = Fool();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
目前的输出是:
左值
右值
但根据我的理解,Fool()返回一个rvalue并且由于const&可以绑定到rvalues所以不需要构造一个 …
我最近了解到成员函数可以是ref-qualified,这使我可以编写
struct S {
S& operator=(S const&) & // can only be used if the implicit object is an lvalue
{
return *this;
}
};
S operator+(S const &, S const &) {
return {};
}
Run Code Online (Sandbox Code Playgroud)
从而阻止用户做类似的事情
S s{};
s + s = S{}; // error
Run Code Online (Sandbox Code Playgroud)
然而,我看到该std::string成员operator= 并没有这样做。所以下面的代码编译时没有警告
std::string s;
s + s = s;
Run Code Online (Sandbox Code Playgroud)
有理由允许这样做吗?
如果没有,将来是否可以添加引用限定符,或者会以某种方式破坏现有代码吗?
请考虑以下代码:
#include<utility>
struct S {
void f(int) = delete;
void f(int) && { }
};
int main() { }
Run Code Online (Sandbox Code Playgroud)
它没有编译说成员方法不能重载,当然它是有道理的.
另一方面,以下代码编译:
#include<utility>
struct S {
void f(int) & = delete;
void f(int) && { }
};
int main() {
S s;
// s.f(42); <-- error, deleted member method
std::move(s).f(42);
}
Run Code Online (Sandbox Code Playgroud)
这是合法代码吗?
是不是可以在同一个类中定义两个完全不同的接口,前者用于左值,后者用于右值?
除了它没有多大意义,但它确实伤害了我.
删除的功能不应该作为一个整体删除,而不是仅在你是左值时删除吗?
这个功能的目的是什么?它是经典的模糊角落还是还有一些我看不到的东西?
我想知道,标准类型的赋值运算符没有左值引用限定是否有原因?他们都不是。
因此,我们可以这样写:
std::string{} = "42";
std::string s = "hello " + std::string{"world"} = "oops!";
std::vector<int> v = { 1,2,3 };
std::move(v) = { 4,5,6 };
Run Code Online (Sandbox Code Playgroud)
如果赋值运算符是左值引用限定的,则所有这些示例都将无法编译。
是因为有很多东西需要修改(但后来是为了 noexcept)而没有人写提案?我不认为人们会写这样的代码,但库不应该被设计成甚至不允许这样做吗?
std::string Concatenate(const std::string& s1,
const std::string& s2,
const std::string& s3,
const std::string& s4,
const std::string& s5)
{
return s1 + s2 + s3 + s4 + s5;
}
Run Code Online (Sandbox Code Playgroud)
默认情况下,return s1 + s2 + s3 + s4 + s5;可能等同于以下代码:
auto t1 = s1 + s2; // Allocation 1
auto t2 = t1 + s3; // Allocation 2
auto t3 = t2 + s4; // Allocation 3
return t3 + s5; // Allocation 4
Run Code Online (Sandbox Code Playgroud)
有一种优雅的方法可以将分配时间减少到1吗?我的意思是保持return s1 + s2 + …
#include <iostream>
struct A
{
void f() const &
{
std::cout << "A::f()&" << std::endl;
}
void f() const &&
{
std::cout << "A::f()&&" << std::endl;
}
};
struct B
{
void f() const &
{
std::cout << "B::f()&" << std::endl;
}
};
int main()
{
A{}.f();
B{}.f();
}
Run Code Online (Sandbox Code Playgroud)
输出是:
A::f()&&
B::f()&
请注意,void B::f() const &&不存在。
对我来说,一个临时对象B的调用B::f,void B::f() const &&应选择或编译器错误应该提高。
为什么void B::f() const &在这种情况下被选中?
据我了解,引用限定的成员函数用于区分隐式操作this作为左值与右值。如果我们定义一个没有 ref 限定的成员函数,那么左值和右值都会调用同一个成员函数。例如
class Obj {
public:
Obj(int x) : x_{x} {}
int& getVal() {return x_;}
};
Run Code Online (Sandbox Code Playgroud)
在这里,做类似的事情Obj o{2}; o.getVal();并Obj{2}.getVal()调用相同的getVal()函数。
但是,我不确定我是否理解这个用例。我似乎看不出我们什么时候可能想要对待与右值不同的左值。我在想也许右值可能被视为临时变量,所以也许我们不想返回对成员变量的引用,而左值可能没问题,因为它们实际上存储在内存地址中并且可以更好地管理。例如,
class Obj {
public:
Obj(int x) : x_{x} {}
int& getVal() & {return x_;}
int getVal() && {return x_;}
};
Run Code Online (Sandbox Code Playgroud)
我相信会返回对左值成员的引用x_,并会返回其右值的副本,Obj因为Obj调用该函数后可能会被删除,并且我们希望保留该返回成员的副本。然而,我想我们也希望有它的右值版本,以防我们要在另一个函数中使用右值引用并且我们想要以某种方式修改该成员。
到目前为止,我对 ref 限定成员函数的解释是否正确?
与如何删除相似的const和非const成员函数之间的代码重复类似?,我想删除几乎相同的成员函数之间的代码重复,除了ref限定符。
假设我有一个类似这样的课程:
class MyStringBuilder
{
std::string member;
public:
// Other functions
std::string create() const& {
// Some work
std::string result = member;
// More work
return result;
}
std::string create() && {
// Some work
std::string result = std::move(member);
// More work
return result;
}
};
Run Code Online (Sandbox Code Playgroud)
我们想对一个构建器对象执行此操作并非不可想象,因为如果使用完成,它将保存一个副本MyStringBuilder。
除了使用成员的位置外,const&版本和&&版本之间的代码相同。这两个函数之间的唯一区别是,只要引用了该&&版本std::move的任何成员,该版本就可以。
如何避免此代码重复?
c++ ×12
ref-qualifier ×12
c++11 ×5
stl ×2
lvalue ×1
overloading ×1
rvalue ×1
std ×1
stdstring ×1