在Herb Sutter的书Exceptional C++(1999)中,他在第10项的解决方案中有词:
"异常不安全"和"糟糕的设计"齐头并进.如果一段代码不是异常安全的,那通常是可以的并且可以简单地修复.但是,如果一段代码由于其底层设计而不能被设置为异常安全,那几乎总是这是其设计不佳的信号.
示例1:具有两种不同职责的职能很难使例外安全.
示例2:以必须检查自我分配的方式编写的复制赋值运算符可能也不是强烈异常安全的
"检查自我分配"一词是什么意思?
[查询]
Dave和AndreyT向我们展示了"检查自我分配"的含义.非常好.但问题还没有结束.为什么"检查自我分配"会伤害"异常安全"(根据Hurb Sutter的说法)?如果调用者尝试进行自我分配,那么"检查"就像没有发生任何分配一样.真的疼吗?
[备忘1]在Herb的书后面的第38项对象身份中,他解释了自我分配.
我正在读一本关于C++的书,更确切地说是关于运算符重载的书.
示例如下:
const Array &Array::operator=(const Array &right)
{
// check self-assignment
// if not self- assignment do the copying
return *this; //enables x=y=z
}
Run Code Online (Sandbox Code Playgroud)
本书提供的关于返回const ref而不是ref的解释是为了避免诸如(x = y)= z之类的赋值.我不明白为什么要避免这种情况.我知道在这个例子中首先计算x = y,因为它返回一个const引用,所以不能执行= z部分.但为什么?
以此示例代码为例:
int a = 10;
int b = 20;
int c = 30;
int & foo1() {
qDebug() << "foo1" << endl;
return a;
}
int & foo2() {
qDebug() << "foo2" << endl;
return b;
}
int & foo3() {
qDebug() << "foo3" << endl;
return c;
}
int main(void)
{
foo1() = foo2() = foo3() = 7;
}
Run Code Online (Sandbox Code Playgroud)
由于任务从右到左,我期望看到foo3第一个和foo1最后一个,但它是相反的.
Are the rules for such scenarios concretely defined and how? Also, does the compiler differentiate …
我已经写了代码:
int x = 18;
x *= 0.90;
System.out.println(x);
Run Code Online (Sandbox Code Playgroud)
这段代码打印出来16
然而,当我写下
int x = 18;
x = x * 0.90;
System.out.println(x);
Run Code Online (Sandbox Code Playgroud)
它给了我以下错误:incompatible types: possible lossy conversion from double to int
我预计这两个代码示例都会导致与 相同的错误x *= y;,x = x * y;但x *= 0.90;不知何故有效,但x = x * 0.90;无效。为什么会这样呢?
这一天发布的帖子让我想知道如何在函数中为全局环境中的多个对象赋值.这是我的尝试使用lapply(assign可能比<<-我更安全,但我从未实际使用它,我不熟悉它).
#fake data set
df <- data.frame(
x.2=rnorm(25),
y.2=rnorm(25),
g=rep(factor(LETTERS[1:5]), 5)
)
#split it into a list of data frames
LIST <- split(df, df$g)
#pre-allot 5 objects in R with class data.frame()
V <- W <- X <- Y <- Z <- data.frame()
#attempt to assign the data frames in the LIST to the objects just created
lapply(seq_along(LIST), function(x) c(V, W, X, Y, Z)[x] <<- LIST[[x]])
Run Code Online (Sandbox Code Playgroud)
请随意缩短我的代码的任何/所有部分以使其工作(或更好/更快地工作).
我已经看到它说一个operator=带有相同类型by-value的参数的文件在C++ 11中既作为复制赋值运算符又作为移动赋值运算符:
Foo& operator=(Foo f)
{
swap(f);
return *this;
}
Run Code Online (Sandbox Code Playgroud)
替代方案的重复次数将超过两倍,并且可能出现错误:
Foo& operator=(const Foo& f)
{
Foo f2(f);
swap(f2);
return *this;
}
Foo& operator=(Foo&& f)
{
Foo f2(std::move(f));
swap(f2);
return *this;
}
Run Code Online (Sandbox Code Playgroud)
在什么情况下,ref-to-const和r-value重载优先通过值,或何时需要?我正在考虑std::vector::push_back,例如,它被定义为两个重载:
void push_back (const value_type& val);
void push_back (value_type&& val);
Run Code Online (Sandbox Code Playgroud)
在第一个示例中,pass by value 用作复制赋值运算符和移动赋值运算符,无法push_back在Standard中定义为单个函数?
void push_back (value_type val);
Run Code Online (Sandbox Code Playgroud) 众所周知,至少在该类具有非POD成员时,在实施分配操作员时必须防止自我分配。通常它是(或等效于):
Foo& operator=(const Foo& other)
{
if (&other == this)
return *this;
... // Do copy
}
Run Code Online (Sandbox Code Playgroud)
不自动插入自我分配保护的原因是什么?是否存在用例中自我分配完成一些琐碎且实际有用的事情?
Foo& operator=(const Foo& other)
{
if (&other == this)
{
// Do something non-trivial
}
else
{
// Do copy
}
return *this;
}
Run Code Online (Sandbox Code Playgroud)
现在总结答案和讨论
看起来非平凡的自我分配永远不会真正有用。提出的唯一选择是放置一个assert,以便检测一些逻辑错误。但是也有像a = std::min(a, b)这样的非常合理的自我分配案例,因此,即使此选项也是非常可疑的。
但是,平凡的自我分配有两种可能的实现方式:
&other == this。始终可以工作,尽管由于额外的分支可能会对性能产生负面影响。但是在用户定义的赋值运算符中,几乎必须始终明确进行测试。我仍然不明白为什么C ++标准不能在用户定义的赋值运算符中保证这一点&other != this。如果不希望分支,请使用默认运算符。如果要重新定义操作员,则仍然需要进行一些测试...
如本回答所述,复制和交换习惯用法如下实现:
class MyClass
{
private:
BigClass data;
UnmovableClass *dataPtr;
public:
MyClass()
: data(), dataPtr(new UnmovableClass) { }
MyClass(const MyClass& other)
: data(other.data), dataPtr(new UnmovableClass(*other.dataPtr)) { }
MyClass(MyClass&& other)
: data(std::move(other.data)), dataPtr(other.dataPtr)
{ other.dataPtr= nullptr; }
~MyClass() { delete dataPtr; }
friend void swap(MyClass& first, MyClass& second)
{
using std::swap;
swap(first.data, other.data);
swap(first.dataPtr, other.dataPtr);
}
MyClass& operator=(MyClass other)
{
swap(*this, other);
return *this;
}
};
Run Code Online (Sandbox Code Playgroud)
通过将MyClass的值作为operator =的参数,可以通过复制构造函数或移动构造函数构造参数.然后,您可以安全地从参数中提取数据.这可以防止代码重复并有助于异常安全.
答案提到您可以在临时中交换或移动变量.它主要讨论交换.但是,交换(如果未由编译器优化)涉及三个移动操作,而在更复杂的情况下,还需要额外的额外工作.当你想要的时候,就是将临时文件移动到assign-to对象中.
考虑这个更复杂的例子,涉及观察者模式.在这个例子中,我手动编写了赋值运算符代码.重点是移动构造函数,赋值运算符和交换方法:
class MyClass : Observable::IObserver …Run Code Online (Sandbox Code Playgroud) 在玩实现虚拟赋值运算符的过程中,我以一种有趣的行为结束了.它不是编译器故障,因为g ++ 4.1,4.3和VS 2005共享相同的行为.
基本上,虚拟运算符=与实际执行的代码相比,其行为与任何其他虚拟函数不同.
struct Base {
virtual Base& f( Base const & ) {
std::cout << "Base::f(Base const &)" << std::endl;
return *this;
}
virtual Base& operator=( Base const & ) {
std::cout << "Base::operator=(Base const &)" << std::endl;
return *this;
}
};
struct Derived : public Base {
virtual Base& f( Base const & ) {
std::cout << "Derived::f(Base const &)" << std::endl;
return *this;
}
virtual Base& operator=( Base const & ) {
std::cout << "Derived::operator=( …Run Code Online (Sandbox Code Playgroud) 我刚刚开始使用C++.我对赋值和解引用运算符的返回类型有点困惑.我正在阅读C++ Primer一书.在不同的场合,作者说,赋值运算符的返回类型是对左手操作数的类型的引用,但后来,他说返回类型是左手操作数的类型.我已经提到了C++ 11标准版.5.17,其中返回类型被描述为"左手操作数的左值".
同样,我无法弄清楚dereference是返回指向对象还是返回对象的引用.
这些陈述是否相同?如果是这样,那怎么样?任何解释将不胜感激.
c++ ×8
c++11 ×2
assign ×1
chaining ×1
dereference ×1
double ×1
environment ×1
global ×1
inheritance ×1
integer ×1
java ×1
lvalue ×1
overloading ×1
r ×1
reference ×1