C++ auto_ptr实现浅谈

最近在看《The C++ Standard Library》。看到C++标准库中的auto_ptr实现问题时,觉得很有趣,于是就打算自己试试,写了一个简单的AutoPointer,叫做SimpleAutoPointer。

代码如下

#ifndef _AUTOPTR1_H
#define _AUTOPTR1_H

template <class ElementType>
class SimpleAutoPointer {
public:
// class for the value
typedef ElementType element_type;

// constructor
explicit SimpleAutoPointer(ElementType* pointer = 0) throw() :
m_Pointer(pointer) {}

// copy constructors
// – note: nonconstant parameter
SimpleAutoPointer(SimpleAutoPointer& rhs) throw() :
m_Pointer(rhs.release()) {}
template <class AnotherElementType>
SimpleAutoPointer(SimpleAutoPointer<AnotherElementType>& rhs) throw() :
m_Pointer(rhs.release()) {}

// assignments
SimpleAutoPointer& operator=(SimpleAutoPointer& rhs) throw(){
reset(rhs.release());
}
template<class AnotherElementType>
SimpleAutoPointer& operator=(SimpleAutoPointer<AnotherElementType>& rhs) throw(){
reset(rhs.release());
}

// destructor
~SimpleAutoPointer() throw(){
if ( m_Pointer ) {
delete m_Pointer;
}
}

// value access
ElementType* get() const throw(){
return m_Pointer;
}
ElementType& operator*() const throw(){
return *m_Pointer;
}
ElementType* operator->() const throw(){
return m_Pointer;
}

// release ownership
ElementType* release() throw(){
ElementType* oldPointer = m_Pointer;
m_Pointer = 0;
return oldPointer;
}

// reset value
void reset(ElementType* newPointer) throw(){
if ( m_Pointer ) {
delete m_Pointer;
}

m_Pointer = newPointer;
}

private:
ElementType* m_Pointer;
};

#endif //_AUTOPTR1_H

很简单,所有的接口全部按照标准库的auto_ptr写的。一开始觉得这段代码应该没有问题,但其实和标准库的auto_ptr行为有一个不同。

我们看看使用这个类的代码,可以看出问题所在

首先是两个简单的类,用来辅助测试

#ifndef _TESTCLASS_H
#define _TESTCLASS_H

#include <iostream>

class BaseClass {
public:
BaseClass() {
std::cout << “Base Constructor” << std::endl;
}

virtual ~BaseClass() {
std::cout << “Base Destructor” << std::endl;
}

virtual void print() const{
std::cout << “Base Class” << std::endl;
}
};

class DerivedClass : public BaseClass {
public:
DerivedClass() {
std::cout << “Derived Constructor” << std::endl;
}

~DerivedClass() {
std::cout << “Derived Destructor” << std::endl;
}

virtual void print() const{
std::cout << “Derived Class” << std::endl;
}
};

#endif //_TESTCLASS_H

然后是使用指针类的代码

    using std::cout;
using std::endl;

// constructor
SimpleAutoPointer<BaseClass> p1(new BaseClass);

// non-const copy constructor
SimpleAutoPointer<BaseClass> p2 = p1;

// non-const assignment constructor
SimpleAutoPointer<BaseClass> p3;
p3 = p2;

// release
SimpleAutoPointer<BaseClass> p4(p3.release());

// value access
cout << p1.get() << endl;
cout << p2.get() << endl;
cout << p3.get() << endl;
cout << p4.get() << endl;

p4->print();
(*p4).print();

// reset
p4.reset(new BaseClass);
cout << p4.get() << endl;

// upper-cast
SimpleAutoPointer<BaseClass> p5(new DerivedClass);
SimpleAutoPointer<DerivedClass> p6(new DerivedClass);

cout << p5.get() << endl;
cout << p6.get() << endl;

p5 = p6;

cout << p5.get() << endl;
cout << p6.get() << endl;

// problem: const copy constructor and assignment operator
// SimpleAutoPointer<BaseClass> p7 = SimpleAutoPointer<BaseClass>(new BaseClass);
// p4 = SimpleAutoPointer<BaseClass>(new BaseClass);

注意最后三行,这里我希望根据一个临时的SimpleAutoPointer<BaseClass>对象构造出一个新的对象,也希望把这一个临时对象赋值给一个已存在的对象。

如果这里没有注释,编译会出错,为什么呢?

看我实现的那个拷贝构造函数和赋值操作符,你会注意到我的类型是SimpleAutoPointer<ElementType>&,而一般的拷贝构造函数应该是const SimpleAutoPointer<ElementType>&,我为什么使用现在的这种参数类型呢?原因是auto_ptr的行为——所有权转移

auto_ptr的拷贝会发生所有权的转移,也即是拷贝的时候被拷贝的对象失去了指针的所有权,这里我们需要修改被拷贝的对象,我们自然不能把类型限定成const reference,否则我就不能修改被拷贝的对象了。

好吧,这里就出现问题了,我代码的最后三行中,需要拷贝的对象是一个rvalue,而rvalue是不能转换成reference传递进函数的,只能被转换成const reference

那我们应该如何解决这个问题呢?这个问题也必须被解决,不然我们的指针对象就不能作为返回值返回……

我的第一个解决方案是mutable

请看MutableAutoPointer的代码

#ifndef _AUTOPTR2_H
#define _AUTOPTR2_H

template <class ElementType>
class MutableAutoPointer {
public:
// class for the value
typedef ElementType element_type;

// constructor
explicit MutableAutoPointer(ElementType* pointer = 0) throw() :
m_Pointer(pointer) {}

// copy constructors
// – note: constant parameter, use mutable member
MutableAutoPointer(const MutableAutoPointer& rhs) throw() :
m_Pointer(rhs.release()) {}
template <class AnotherElementType>
MutableAutoPointer(const MutableAutoPointer<AnotherElementType>& rhs) throw() :
m_Pointer(rhs.release()) {}

// assignments
// – note: constant parameter, use mutable member
MutableAutoPointer& operator=(const MutableAutoPointer& rhs) throw(){
reset(rhs.release());
}
template<class AnotherElementType>
MutableAutoPointer& operator=(const MutableAutoPointer<AnotherElementType>& rhs) throw(){
reset(rhs.release());
}

// destructor
~MutableAutoPointer() throw(){
if ( m_Pointer ) {
delete m_Pointer;
}
}

// value access
ElementType* get() const throw(){
return m_Pointer;
}
ElementType& operator*() const throw(){
return *m_Pointer;
}
ElementType* operator->() const throw(){
return m_Pointer;
}

// release ownership
ElementType* release() const throw(){
ElementType* oldPointer = m_Pointer;
m_Pointer = 0;
return oldPointer;
}

// reset value
void reset(ElementType* newPointer) throw(){
if ( m_Pointer ) {
delete m_Pointer;
}

m_Pointer = newPointer;
}

private:
// – note: The pointer is mutable
mutable ElementType* m_Pointer;
};

#endif //_AUTOPTR2_H

那些地方进行了修改呢?

首先是我的拷贝构造函数和赋值操作符,所有的参数都改成了const reference。然后我把release函数加上了const修饰。

最关键的一点是,在m_Pointer上加上了mutable修饰符,这样我们就可以在只读的对象中修改这个值了。

测试

    using std::cout;
using std::endl;

// constructor
MutableAutoPointer<BaseClass> p1(new BaseClass);

// non-const copy constructor
MutableAutoPointer<BaseClass> p2 = p1;

// non-const assignment constructor
MutableAutoPointer<BaseClass> p3;
p3 = p2;

// release
MutableAutoPointer<BaseClass> p4(p3.release());

// value access
cout << p1.get() << endl;
cout << p2.get() << endl;
cout << p3.get() << endl;
cout << p4.get() << endl;

p4->print();
(*p4).print();

// reset
p4.reset(new BaseClass);
cout << p4.get() << endl;

// upper-cast
MutableAutoPointer<BaseClass> p5(new DerivedClass);
MutableAutoPointer<DerivedClass> p6(new DerivedClass);

cout << p5.get() << endl;
cout << p6.get() << endl;

p5 = p6;

cout << p5.get() << endl;
cout << p6.get() << endl;

// solve problem: const copy constructor and assignment operator
MutableAutoPointer<BaseClass> p7 = MutableAutoPointer<BaseClass>(new BaseClass);
p4 = MutableAutoPointer<BaseClass>(new BaseClass);

// new problem: user can copy the constant pointer object!!!
const MutableAutoPointer<BaseClass> p8(new BaseClass);
p7 = p8;

好了,第43和44行,可以编译通过了,运行也没有问题。

大家以为这样就解决了问题了吗?其实不然,看看48行,出现了一个新问题。其实也是我们本身的设计上有问题。

我可以把一个常量赋值给一个变量,然后常量改变了

这是我们不希望出现的,我们希望常量具有不变性(无论是位不变,还是逻辑不变,这里都是有问题的!),但这里明显违反了。

其实也是我们自身的设计有问题,我们本来是希望,能够把一个rvalue赋值给我们的pointer对象,但是这里造成了一个副作用:我们可以修改常量

根本原因是我们使用了mutable

那有没有什么其他的解决方案呢。

在C++98和C++03中,我的解决方案如下(实在不行,参考了标准库的实现……)

AutoPointer的代码

#ifndef _AUTOPTR3_H
#define _AUTOPTR3_H

// The class help us convert rvalue to lvalue
template <class ElementType>
class AutoPointerReference {
public:
ElementType* m_Pointer;
AutoPointerReference(ElementType* pointer) :
m_Pointer(pointer) {}
};

template <class ElementType>
class AutoPointer {
public:
// class for the value
typedef ElementType element_type;

// constructor
explicit AutoPointer(ElementType* pointer = 0) throw() :
m_Pointer(pointer) {}

// copy constructors
// – note: nonconstant parameter
AutoPointer(AutoPointer& rhs) throw() :
m_Pointer(rhs.release()) {}
template <class AnotherElementType>
AutoPointer(AutoPointer<AnotherElementType>& rhs) throw() :
m_Pointer(rhs.release()) {}

// assignments
AutoPointer& operator=(AutoPointer& rhs) throw(){
reset(rhs.release());
}
template<class AnotherElementType>
AutoPointer& operator=(AutoPointer<AnotherElementType>& rhs) throw(){
reset(rhs.release());
}

// destructor
~AutoPointer() throw(){
if ( m_Pointer ) {
delete m_Pointer;
}
}

// value access
ElementType* get() const throw(){
return m_Pointer;
}
ElementType& operator*() const throw(){
return *m_Pointer;
}
ElementType* operator->() const throw(){
return m_Pointer;
}

// release ownership
ElementType* release() throw(){
ElementType* oldPointer = m_Pointer;
m_Pointer = 0;
return oldPointer;
}

// reset value
void reset(ElementType* newPointer) throw(){
if ( m_Pointer ) {
delete m_Pointer;
}

m_Pointer = newPointer;
}

// special conversions with auxiliary type to enable copies and assignments
AutoPointer(AutoPointerReference<ElementType> rhs) throw() :
m_Pointer(rhs.m_Pointer) {}
AutoPointer& operator=(AutoPointerReference<ElementType> rhs) throw() {
reset(rhs.m_Pointer);
}

template <class SameElementType>
operator AutoPointerReference<SameElementType>() throw() {
return AutoPointerReference<SameElementType>(release());
}

template <class AnotherElementType>
operator AutoPointer<AnotherElementType>() throw() {
return AutoPointer<AnotherElementType>(release());
}
private:
ElementType* m_Pointer;
};

#endif //_AUTOPTR3_H

注意看,我增加了一个AutoPointerReference类,这个类用来干吗呢?它的任务是将一个pointer对象的rvalue转换成一个可修改的lvalue

我是如何做到的呢,其实很简单,关键就在75行之后的那几行代码。分别是针对AutoPointerReference对象的构造函数和赋值操作符。

注意,我这里的构造函数没有加上explicit,也就是说这个构造函数会被偷偷地调用,在你不知道的时候。

然后我写了两个类型转换函数,第一个是把本身转换成对应的AutoPointerReference对象,第二个用来向上转型。

然后看看测试

using std::cout;
using std::endl;

// constructor
AutoPointer<BaseClass> p1(new BaseClass);

// non-const copy constructor
AutoPointer<BaseClass> p2 = p1;

// non-const assignment constructor
AutoPointer<BaseClass> p3;
p3 = p2;

// release
AutoPointer<BaseClass> p4(p3.release());

// value access
cout << p1.get() << endl;
cout << p2.get() << endl;
cout << p3.get() << endl;
cout << p4.get() << endl;

p4->print();
(*p4).print();

// reset
p4.reset(new BaseClass);
cout << p4.get() << endl;

// upper-cast
AutoPointer<BaseClass> p5(new DerivedClass);
AutoPointer<DerivedClass> p6(new DerivedClass);

cout << p5.get() << endl;
cout << p6.get() << endl;

p5 = p6;

cout << p5.get() << endl;
cout << p6.get() << endl;

// solve problem: const copy constructor and assignment operator
AutoPointer<BaseClass> p7 = AutoPointer<BaseClass>(new BaseClass);
p4 = AutoPointer<BaseClass>(new BaseClass);

// solve problem: user can’t copy the constant pointer object
// const AutoPointer<BaseClass> p8(new BaseClass);
// p7 = p8;

第43,44行可以通过,47,48行无法通过。

那么为什么这样可以呢?

其实AutoPointer<BaseClass>(new BaseClass)产生的是一个可修改的rvalue,现在它要被传递给AutoPointer<BaseClass>的一个函数(构造或 者赋值),由于不可能匹配reference的那个版本,所以只能匹配AutoPointerReference的那个版本,编译器就为了匹配自动进行隐 式转换,将我们的AutoPointer对象先转换成对应的AutoPointerReference。由于转换时,我调用了release,原来的那个 对象会失去它的所有权(注意,这个对象是可修改的右值,所以我们是可以改变它的),然后再把这个AutoPointerReference对象传递进来,交给对应的函数处理。

那为什么48就不行了呢?

因为p8是一个常量,我们的类型转换操作符是非const的,常量不能使用

OK,问题解决!

这里牵涉到lvalue,rvalue,可修改/不可修改,成员模板,编译器自动推导,函数重载,隐式类型转换等等问题,稍稍有点麻烦。

那有没有比较简单的解决方案呢?

在C++11里可以,我们来看看,这段使用了C++11特性的代码:

#ifndef _AUTOPTR4_H
#define _AUTOPTR4_H

// The version of C++ 11
template <class ElementType>
class AutoPointer11 {
public:
// class for the value
typedef ElementType element_type;

// constructor
explicit AutoPointer11(ElementType* pointer = 0) throw() :
m_Pointer(pointer) {}

// copy constructors
// – note: nonconstant parameter
AutoPointer11(AutoPointer11& rhs) throw() :
m_Pointer(rhs.release()) {}
template <class AnotherElementType>
AutoPointer11(AutoPointer11<AnotherElementType>& rhs) throw() :
m_Pointer(rhs.release()) {}

// assignments
AutoPointer11& operator=(AutoPointer11& rhs) throw(){
reset(rhs.release());
}
template<class AnotherElementType>
AutoPointer11& operator=(AutoPointer11<AnotherElementType>& rhs) throw(){
reset(rhs.release());
}

// destructor
~AutoPointer11() throw(){
if ( m_Pointer ) {
delete m_Pointer;
}
}

// value access
ElementType* get() const throw(){
return m_Pointer;
}
ElementType& operator*() const throw(){
return *m_Pointer;
}
ElementType* operator->() const throw(){
return m_Pointer;
}

// release ownership
ElementType* release() throw(){
ElementType* oldPointer = m_Pointer;
m_Pointer = 0;
return oldPointer;
}

// reset value
void reset(ElementType* newPointer) throw(){
if ( m_Pointer ) {
delete m_Pointer;
}

m_Pointer = newPointer;
}

// move constructor and assignment
// – note: only support mutable rvalue
AutoPointer11(AutoPointer11<ElementType>&& rhs) :
m_Pointer(rhs.release()) {}
AutoPointer11& operator=(AutoPointer11<ElementType>&& rhs) {
reset(rhs.release());
}

private:
ElementType* m_Pointer;
};

#endif //_AUTOPTR4_H

第68行开始,我使用了C++11里面的右值引用。不过这里偷懒了,所以没有加上向上转型的对应版本。

哈哈,你会发现,这个解决方案就和谐多了,没有了牵涉到隐式转换和重载的恶心问题,直接使用右值引用的特性来操作可修改的右值,这确实是C++11的一个非常好的地方。

如果你还不知道什么是右值引用,自己上网查吧……

 

小结:

首先,以上几个指针类代码是基于The C++ Standard Library中的接口写的,自己写了实现。

其实,C++的lvalue/rvalue是一个非常非常复杂的问题,结合类型转换,模板,可以把问题变得更加复杂……这可能就是C++的恐怖的地方,当很多晦涩的概念交织在一起,代码就容易出错……

当然,现在C++11中已经不使用auto_ptr了,因为使用它太容易出现一些隐蔽的bug。而是使用去掉了所有权转移的unique_ptr,我这里只是玩玩,所以探究了一下auto_ptr的实现问题。

感觉C++学习的路还长着额……

anyShare分享到:
This entry was posted in 学习笔记. Bookmark the permalink.

发表评论