关于 operator=(), swap 和 栈对象 的 异常安全问题
在重写 类的 operator=()时, 一般会注意异常安全问题,使用 swap, RAII 手法进行设计, 如:
class Foo
{
Foo();
Foo( const Foo& other);
~Foo();
void MySwap( Foo& other);
Foo& operator=( const Foo& other)
{
//... 自赋值等检测
Foo temp( other);
MySwap( temp); // 问题在这里
return *this;
}
};
Foo& operator=( const Foo other)
{
MySwap( other );
return *this;
}
T temp(other);//分配资源,复制对象
swap(temp);//无代价
return * this;//返回引用,无代价
}//析构temp,释放资源
#include <fstream>
#include <iostream>
int main ()
{
std::ostream* const os = new std::ofstream("test.txt");
delete static_cast<std::ofstream*>(os);
}
class ostream_ptr
{
public:
ostream_ptr(ostream * os)
: my_os(os)
{
}
~XXX()
{
//我现在应该如何释放这个os?
}
private
ostream * my_os;
};
进行 当前对象的数据内容 与 临时栈对象 temp 的数据内容 进行交换,this的数据存放于temp的栈上, 当退出该函数后, this的数据不就指向 在无效的 栈上了吗? 否的话, 又为什么?
void MySwap(MyType& other)
{
if(this == &other) return;
//....
auto t = this->a;
this->a = other.a;
other.a = t;
//如果a的类型是T, 则拷贝,没有问题
//如果a的类型是T*, 则交换地址,没有问题
//如果a的类型是T&, 则拷贝,没有问题
//....
}
iostream的问题很复杂,它有派生,却没有虚析构,所以我们不能new一个ofstream然后保存在一个ostream指针里面……它的派生关系只是为了能把一个ofstream对象传递给一个ostream&参数,而在拥有这个流对象(也就是负责释放这个对象)的代码中,必须保留着它的原始类型。iostream通常以值对象的方式存在,但它却又不能赋值……
iostream这个自C++诞生就存在的东西其实有很多不合理的地方。
为啥?你的意思是说下面的代码不能编译吗。
#include <fstream>
#include <iostream>
int main ()
{
std::ostream* const os = new std::ofstream("test.txt");
delete static_cast<std::ofstream*>(os);
}
这个代码当然可以编译。但是我的某个类为了使用RAII技术而设计下面这个类:
class ostream_ptr
{
public:
ostream_ptr(ostream * os)
: my_os(os)
{
}
~XXX()
{
//我现在应该如何释放这个os?
}
private
ostream * my_os;
};
事实上,我们更常使用的是auto_ptr<base_class>或unique_ptr<base_class>,但auto_ptr<ostream>一定不会知道如何正确地释放一个ofstream。
因此,我们用ostream这一类没有虚析构但又有派生关系的类的对象指针的时候,一定要“谁分配谁释放”,既然如此,我为什么不干脆使用值对象呢?
此外,ostream不允许拷贝赋值操作符。
不能把各种ostream以基类指针的方式传递给其它对象,另它不象“真正的对象”;不支持拷贝赋值,另它不象“值对象”,所有说它是一种很特别的存在。
你的结论貌似都是基于这一条认识,即 type<base_t> object(new derived_t); 的模式无法正确释放 new derived_t 对象。我想说这是没有根据的,比如至少可以这样做。
#include <iostream>
struct deleter_base_t
{
virtual ~deleter_base_t () { }
};
template <typename T>
struct deleter_t final : deleter_base_t
{
~deleter_t () { delete m_ptr; }
deleter_t (T* ptr) : m_ptr(ptr) { }
private:
T* m_ptr;
};
template <typename static_t>
struct ostream_ptr_impl_t
{
~ostream_ptr_impl_t () { delete m_delete; }
template <typename dynamic_t>
ostream_ptr_impl_t (dynamic_t* os)
: m_delete(new deleter_t<dynamic_t>(os))
{
}
private:
deleter_base_t* m_delete;
};
class ostream_ptr_t
{
public:
template <typename T>
ostream_ptr_t (T* os) : m_os(os)
{
}
private:
ostream_ptr_impl_t<std::ostream> m_os;
};
namespace test
{
struct ostream
{
~ostream () { std::cout << "~ostream ()" << std::endl; }
ostream () { std::cout << " ostream ()" << std::endl; }
};
struct ofstream : ostream
{
~ofstream () { std::cout << "~ofstream ()" << std::endl; }
ofstream () { std::cout << " ofstream ()" << std::endl; }
};
}
int main ()
{
ostream_ptr_t test(new test::ofstream);
}
你后面一系列的推论也因此都不可靠了。
另外,ostream 不支持复制不是讨论的重点,为了避免不必要的跑题,完全可以把 std::ostream/std::ofstream 换成上例中 test::ostream/test::ofstream。
我们一直在讨论你 #9 给出的例子代码中体现的问题,是否有必要只针对具有虚析构函数的类而言(你的观点),还是这种问题存在于任何继承体系中(我的观点),因此没必要只针对前者。
#include <iostream>
struct deleter_base_t
{
virtual ~deleter_base_t () { }
};
template <typename T>
struct deleter_t final : deleter_base_t
{
~deleter_t () { delete m_ptr; }
deleter_t (T* ptr) : m_ptr(ptr) { }
private:
T* m_ptr;
};
template <typename static_t>
struct ostream_ptr_impl_t
{
~ostream_ptr_impl_t () { delete m_delete; }
template <typename dynamic_t>
ostream_ptr_impl_t (dynamic_t* os)
: m_delete(new deleter_t<dynamic_t>(os))
{
}
private:
deleter_base_t* m_delete;
};
namespace test
{
struct ostream
{
~ostream () { std::cout << "~test::ostream ()" << std::endl; }
ostream () { std::cout << " test::ostream ()" << std::endl; }
};
struct ofstream : ostream
{
~ofstream () { std::cout << "~test::ofstream ()" << std::endl; }
ofstream () { std::cout << " test::ofstream ()" << std::endl; }
};
}
class ostream_ptr_t
{
public:
template <typename T>
ostream_ptr_t (T* os) : m_os(os)
{
}
private:
ostream_ptr_impl_t<test::ostream> m_os;
};
int main ()
{
{ostream_ptr_t test(new test::ofstream);}
{ostream_ptr_t test(new test::ostream);}
}