boost::noncopyable
禁止拷贝语义
对于一个empty class,编译器会生成默认的五个函数
//自定定义
class empty_class
{
};
//编译器自动生成后真实代码
class empty_class
{
public:
empty_class(){} //构造函数
empty_class (const empty_class&){...} //拷贝构造函数
empty_class(empty_class&&){...} //移动构造函数
empty_class& operator= (empty_class&&){...} //移动赋值函数
empty_class& operator= (const empty_class&){...} //拷贝赋值函数
};
在有些情况下,我们不需要类的复制语义,便只需要将复制构造函数和复制赋值操作符私有化,或定义成delete:
class do_not_copy_class
{
private:
//私有化 声明即可 不用实现
empty_class (const empty_class&);
empty_class& operator= (const empty_class&);
};
但是如果程序中有大量这样的类,代码重复冗余太多,可采用一种更优雅的方法
继承boost::noncopyable
实现一个不可复制的类很简单,只需要从boost::noncopyable类继承即可。noncopyable位于命名空间boost,使用时需要包含头文件<boost/noncopyable>或者<boost/utility.hpp>:
#include <iostream>
#include <boost/noncopyable.hpp>
//这里使用默认的私有继承是可以的
//当然我也可以显示写出private或者publiic修饰词,但效果相同的
class do_not_copy_class : boost::noncopyable
{
};
int main(int argc, char *argv[])
{
do_not_copy_class test1;
//编译错误 调用拷贝构造函数
do_not_copy_class test2(test1);
do_not_copy_class test3;
//编译错误 调用拷贝赋值操作符
test3 = test1;
return 0;
}
boost::noncopyable的实现
class noncopyable
{
protected:
//默认的构造函数和析构函数是保护的
noncopyable() {}
~noncopyable() {}
private:
//私有化复制构造函数和复制赋值操作符
noncopyable( const noncopyable& );
const noncopyable& operator=( const noncopyable& );
};
boost::noncopyable的构造函数和析构函数是protected的,可以在子类中调用,从而完成子类的初始化和析构。
boost::noncopyable的拷贝构造函数和拷贝赋值是private的,子类无法访问,因此子类也无法拷贝构造和拷贝赋值。
如果使用C++11标准中的defalut和delete关键字,也可将boost::noncopyable的实现改为如下形式:
class noncopyable
{
protected:
//默认的构造函数和析构函数是保护的
//使用编译器的自动产生的默认实现
noncopyable() = default;
~noncopyable() = default;
//使用delete关键字禁止编译器自动产生复制构造函数和复制赋值操作符
noncopyable( const noncopyable& ) = delete;
const noncopyable& operator=( const noncopyable& ) = delete;
};
boost::enable_shared_from_this
返回this指针
一般来说,我们不能直接将this指针返回。因为我们可能在对象的生命周期外使用this指针。如果对象析构了,外部变量使用这个指针,就会使得程序崩溃。
使用智能指针
那么如果返回一个智能指针如何?当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就传递一个指向自身的share_ptr:
#include <iostream>
#include <boost/shared_ptr.hpp>
class Test
{
public:
//构造函数
Test() { std::cout << "Test Constructor." << std::endl; }
//析构函数
~Test() { std::cout << "Test Destructor." << std::endl; }
//获取指向当前对象的指针
boost::shared_ptr<Test> GetObject()
{
boost::shared_ptr<Test> pTest(this);
return pTest;
}
};
int main(int argc, char *argv[])
{
{
boost::shared_ptr<Test> p( new Test( ));
std::cout << "p.use_count(): " << p.use_count() << std::endl;
boost::shared_ptr<Test> q = p->GetObject();
std::cout << "q.use_count(): " << q.use_count() << std::endl;
}
return 0;
}
/** 程序输出结果
Test Constructor.
p.use_count(): 1
q.use_count(): 1
Test Destructor.
Test Destructor.
*/
在上面的demo中,对象只构造了一次,但却析构了两次。并且在增加一个指向的时候,shared_ptr的计数并没有增加。p和q都认为自己是Test指针的唯一拥有者,这两个shared_ptr在计数为0的时候,都会调用一次Test对象的析构函数,所以会出问题。
类对象由外部函数通过某种机制分配的,而且一经分配立即交给shared_ptr管理,而且以后凡是需要共享使用类对象的地方,必须使用这个shared_ptr当作右值来构造产生或者拷贝产生(shared_ptr类中定义了赋值运算符函数和拷贝构造函数)另一个shared_ptr ,从而才能达到共享使用的目的。
使用shared_from_this()
问题是:如何在类对象内部中获得一个指向当前对象的shared_ptr 对象?
答案是:继承boost::enable_share_from_this,然后通过其成员函数 share_from_this()返回当指向自身的share_ptr。
#include <iostream>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
class Test : public boost::enable_shared_from_this<Test>
//继承boost::enable_shared_from_this
{
public:
//构造函数
Test() { std::cout << "Test Constructor." << std::endl; }
//析构函数
~Test() { std::cout << "Test Destructor." << std::endl; }
//获取指向当前对象的指针
boost::shared_ptr<Test> GetObject()
{
return shared_from_this(); //使用shared_from_this()
}
};
int main(int argc, char *argv[])
{
{
boost::shared_ptr<Test> p( new Test( ));
std::cout << "p.use_count(): " << p.use_count() << std::endl;
boost::shared_ptr<Test> q = p->GetObject();
std::cout << "q.use_count(): " << q.use_count() << std::endl;
}
return 0;
}
/** 程序输出结果
Test Constructor.
p.use_count(): 1
q.use_count(): 2
Test Destructor.
*/
一切正常。构造和析构函数都只被调用了一次,p和q也实现了资源共享。
enable_shared_from_this的原理
在enable_share_from_this,里有一个成员weak_this_enable_share_from_this,存储了管理该对象的share_ptr的引用计数,通过weak_ptr来实现。
这个weak_ptr在share_ptr的构造函数中被初始化:
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn() // Y must be complete
{
boost::detail::sp_pointer_construct( this, p, pn );
}
template< class T, class Y > inline void sp_pointer_construct( boost::shared_ptr< T > * ppx, Y * p, boost::detail::shared_count & pn )
{
boost::detail::shared_count( p ).swap( pn );
boost::detail::sp_enable_shared_from_this( ppx, p, p );
}
template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx, Y const * py, boost::enable_shared_from_this< T > const * pe )
{
if( pe != 0 )
{
pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) );
}
}
template<class X, class Y> void _internal_accept_owner(shared_ptr<X> const * ppx, Y * py) const
{
if (weak_this_.expired())
{
weak_this_ = shared_ptr<T>(*ppx, py);
}
}
这里对enable_shared_from_this 的成员 weak_this_ 进行拷贝赋值,使得整个 weak_ptr 作为类对象shared_ptr 的一个观察者。当调用share_from_this()时,就可以从这个 weak_ptr 来生成了,且引用计数是相同的
shared_ptr<T const> shared_from_this() const
{
shared_ptr<T const> p( weak_this_ );
BOOST_ASSERT( p.get() == this );
return p;
}
C++11的支持
在C++11中, enable_shared_from_this是一个模板类,定义于头文件。
std::enable_shared_from_this与std::shared_ptr配合使用:
template< class T > class enable_shared_from_this;
boost::any
any类的设计要点
C++是强类型语言,对型别的要求都是苛刻的,只有当型别之间提供了转换操作符或是标准所允许的一定程度的隐式转换才被允许。
如何设计一个类,实现类型擦除(),能够保存任意类型变量:
int i;
iong j;
X x; //X为用户定义的类
any anyVal=i;
anyVal=j;
anyVal=x;
实现这样一个any类,需要满足下列要求:
1)any类不能是一个模板类形,如int i;any<int> anyValue=i;这样无意义
2)any类要提供一个模板构造函数和模板operator=操作符,允许下列写法:
any any_value(val_1);
any_value=val_2;
3)any类必须提供某些有关它所保存的对象型别的信息
4)any类必须提供某种方法将它保存的数值“取出来”
5)数据的存放:不能将它保存在any类中,那会导致any类成为模板类。
数据应该动态存放,即动态分配一个数据的容器来存放数据,而any类中则保存指向这个容器的指针。
明确地说,是指向这个容器的基类的指针,这是因为容器本身必须为模板,而any类中的指针成员又必须不是泛型的
结论是:为容器准备一个非泛型的基类,而让指针指向该基类。
any类的实现
class any
{
public:
//保存真正数据的接口类
class placeholder
{
public:
virtual ~placeholder()
{
}
public:
virtual const std::type_info & type() const = 0;
virtual placeholder * clone() const = 0;
};
//真正保存和获取数据的类。
template<typename ValueType>
class holder : public placeholder
{
public:
holder(const ValueType & value): held(value)
{
}
virtual const std::type_info & type() const
{
return typeid(ValueType);
}
virtual placeholder * clone() const
{
return new holder(held);//使用了原型模式
}
public:
//真正的数据,就保存在这里
ValueType held;
};
public:
any(): content(NULL)
{
}
//模板构造函数,参数可以是任意类型,真正的数据保存在content中
template<typename ValueType>
any(const ValueType & value): content(new holder<ValueType>(value))
{
}
//拷贝构造函数
any(const any & other)
: content(other.content ? other.content->clone() : 0)
{
}
//析构函数,删除保存数据的content对象
~any()
{
if(NULL != content)
delete content;
}
private:
//一个placeholde对象指针,指向其子类folder的一个实现
// 即content( new holder<ValueType>(value) )语句
placeholder* content;
template<typename ValueType> friend ValueType any_cast(const any& operand);
public:
//查询真实数据的类型。
const std::type_info & type() const
{
return content ? content->type() : typeid(void);
}
};
//获取content->helder数据的方法。用来获取真正的数据
template<typename ValueType>
ValueType any_cast(const any& operand)
{
assert( operand.type() == typeid(ValueType) );
return static_cast<any::holder<ValueType> *>(operand.content)->held;
}
实现any的功能主要由三部分组成:
1)any类
2)真正保存数据的holder类及其基类placeholder
3)获取真正数据的模板函数any_cast,类型转换的功能
any类的应用:伪虚模板函数
C++中没有提供virtual template function,可使用any类代替这种功能:
class Base
{
public:
virtual void Accept(boost::any anyData)
{
...
}
};
class Derived:public Base
{public:
virtual void Accept(boost::any anyData)
{
...
}
};
C++17的支持
any类目前已经加入到c++17标准中,#include <any>头文件即可使用std::any
boost::variant
variant类的设计要点
如何用一个变量来保存和操作不同类型的数据。
1)通过继承:但继承意味着使用指针或引用,除了麻烦和可能引起的效率问题,该做法最大的不便还在语义上,指针和引用都不是值类型。
2)union:对简单类型来说是很好的解决思路,但它没法很好地支持用户自定义的类型,不能方便和智能地调用自定义类型的构造和析构函数。
vatiant便是这样一种解决方案:继承了union的所有优点,还可以存放任意类型的值,不用担心构造和析构的问题。
variant需满足以下需求:
1)支持任意数量的类型,能像简单类型一样对其赋不同类型的值。
2)通过 variant::get<type>() 这样的方式来获取保存在里面的值。
3)支持获取指针(从而类型错误时不用抛异常),支持emplace_set()(类似 vector 里的 emplace_back()).
4)支持隐式构造,支持copy和move语义。
即支持类似下列形式的用法
// 构造
Variant<int, double, string> v1(32);
Variant<int, double, string> v2 = string("www");
Variant<int, double, string> v3(v2);
Variant<int, string> v4("abc");
int k = v1.GetRef<int>();
assert(k == 32);
string& s = v2.GetRef<string>();
assert(s == "www");
assert(v3.GetRef<string>() == "www");
assert(2, v4.GetType());
assert(v4.GetRef<string>() == "abc");
// 赋值
v1 = 23;
assert(v1.GetRef<int>() == 23);
v1 = "eee";
assert(v1.GetRef<string>() == "eee");
v1.emplace_set<string>(4, 'a');
assert(v1.GetRef<string>() == "aaaa");
// 拷贝
v1 = v2;
assert(v1.GetRef<string>() == "www");
assert(v2.GetRef<string>() == "www");
// move
v2 = std::move(v1);
assert(v2.GetRef<string>() == "www");
assert(v1.Get<string>() == nullptr);
Variant<int, double, string> v5(std::move(v2));
assert(v5.GetRef<string>() == "www");
assert(v2.Get<string>() == nullptr);
variant类的实现
支持任意数量类型
内存对齐
template <typename ...TS> struct TypeMaxSize;
template <>
struct TypeMaxSize<>
{
static constexpr std::size_t value = 0;
static constexpr std::size_t align = 0;
};
template <typename T, typename ...TS>
struct TypeMaxSize<T, TS...>
{
static constexpr std::size_t cur = sizeof(T);
static constexpr std::size_t next = TypeMaxSize<TS...>::value;
static constexpr std::size_t value = cur > next? cur : next;
static constexpr std::size_t cur_align = alignof(T);
static constexpr std::size_t next_align = TypeMaxSize<TS...>::value;
static constexpr std::size_t align = cur_align > next_align? cur_align : next_align;
};
template<class ...TS>
struct variant_t
{
private:
constexpr static size_t Alignment() { return TypeMaxSize<TS...>::align; }
constexpr static size_t TypeSize() { return TypeMaxSize<TS...>::value; }
private:
alignas(Alignment()) unsigned char data_[TypeSize()];
};
内部数据以char数组方式存储,以模板递归的形式获取所有可变参数中,最大的对齐为对齐,最大的元素大小为大小。
标记类型
需要一种方式来标记当前保存的是哪种类型的数据:
1)可以选择使用typeid()来作为类型的 tag,
这样模板的参数顺序就变得不重要了,类型重复也影响不大。
2)也可利用类型在模板参数列表中的位置作为该类型在Variant中的 id,直观简单。
此时模板的参数顺序就有影响了。
Variant<int, double> 就不能写成Variant<double, int>
在编译期计算id,检查某个类型是否存在于模板的变长参数列表:
// check if a type exists in the variadic type list
template <typename T, typename ...TS> struct TypeExist;
template <typename T>
struct TypeExist<T>
{
enum { exist = 0 };
static constexpr std::size_t id = 0;
};
template <typename T, typename T2, typename ...TS>
struct TypeExist<T, T2, TS...>
{
enum { exist = std::is_same<T, T2>::value || TypeExist<T, TS...>::exist };
static constexpr std::size_t id = std::is_same<T, T2>::value? 1 : 1 + TypeExist<T, TS...>::id;
};
构造函数
template<class ...TS>
struct variant_t
{
template<class T>
variant_t(T&& v): type_(TypeExist<T, TS...>::id
{
static_assert(TypeExist<T,TS...>::exist, "invalid type for Variant.");
// placement new to construct an object of T.
new(data_) typename std::remove_reference<T>::type(std::forward<T>(v));
}
private:
constexpr static size_t Alignment() { return TypeMaxSize<TS...>::value; }
constexpr static size_t TypeSize() { return TypeMaxSize<TS...>::value; }
private:
size_t type_ = 0;
alignas(Alignment()) unsigned char data_[TypeSize()];
};
使用内存对齐的char数组作为存储空间。
模板构造函数初始化列表中,是使用构造函数模板参数在Variant模板参数列表中的位置初始化type_。
编译期断言构造函数模板参数在Variant模板参数列表中存在。
使用placement new在char数组上构造元素。
析构函数
template<class ...TS>
struct variant_t
{
// other definition....
~variant_t()
{
Release();
}
// other definition....
private:
void Release()
{
if (type_ == 0) return;
destroy_[type_ - 1](data_);
}
private:
size_t type_ = 0;
using destroy_func_t = void(*)(unsigned char*);
// 声明,需在结构体外再定义。
constexpr static destroy_func_t destroy_[] = {destroy<TS>...};
alignas(Alignment()) unsigned char data_[Alignment()];
};
// 定义 constexpr 数组。
template<class ...TS>
constexpr variant_t<TS...>::destroy_func_t variant_t<TS...>::fun[];
元素销毁的函数形式类似,只需调用对应元素的析构函数。
为Variant的每个模板参数定义一个元素销毁函数,并组成数组,以type_来索引。析构之时只需要调用对应的函数即可。
隐式构造与类型转换
为了使Variant<int, string> v("abc")这种语句合法,还需要一个能转换类型的东西。
它能根据构造函数的模板参数T,从Variant的模板参数列表中选择一个类型CT,使得T能隐式地转换为CT。
1)判断一种类型是否可转换为另一种类型
template<class S, class D>
struct is_convertible
{
struct big { char d[2]; };
typedef char small;
static S get_src_type();
static big foo(D);
static small foo(...);
enum { value = sizeof(foo(get_src_type())) == sizeof(big) };
};
利用函数的重载规则:如果S可隐式转换为D类型。
根据函数重载规则foo的调用将是返回big版本的,如果不同,则是small版本的。以此判断两个类型是否可转换。
std::is_convertible<T>实现了相同功能。
2)从模板参数列表中选择能转换的类型
template<class T, class ...TS>
struct SelectConvertible
{
enum { exist = false };
using type = void;
};
template<class T, class T1, class ...TS>
struct SelectConvertible<T, T1, TS...>
{
enum { exist = std::is_convertible<T, T1>::value || SelectConvertible<T, TS...>::exist };
using type = typename std::conditional<std::is_convertible<T, T1>::value,
T1, typename SelectConvertible<T, TS...>::type>::type ;
};
template<class T, class ...TS>
struct SelectType
{
using type = typename std::conditional<TypeExist<T, TS...>::exist, T,
typename SelectConvertible<T, TS...>::type>::type;
};
使用std::is_convertible<T>,利用模板递归的方式,容易实现选择可转换类型的功能。
拷贝与移动
为了支持拷贝与移动语义,需要实现以下四个函数
template<class ...TS>
struct variant_t
{
variant_t(variant<TS...>&& v);
variant_t(const variant_t<TS...>& v);
variant_t& operator=(variant_t<TS...>&& v);
variant_t& operator=(const variant_t<TS...>& v);
}
之前定义的模板构造函数可以接受不同类型的值,现在再定义参数类型为variant_t<>的构造函数会和它冲突。
因此必须让前面的模板构造函数不接受variant_t<>这种类型作为模板参数:
template<class ...TS>
struct variant_t
{
template<class T, class D = typename std::enable_if<
!std::is_same<typename std::remove_reference<T>::type, Variant<TS...>>::value>::type>
variant_t(T&& v): type_(TypeExist<T, TS...>::id
{
static_assert(TypeExist<T,TS...>::exist, "invalid type for Variant.");
// placement new to construct an object of T.
new(data_) typename std::remove_reference<T>::type(std::forward<T>(v));
}
// other definition....
};
接下来,参考析构函数的实现,也可使用type_索引数组的方式,完成拷贝和移动相关的函数
template<class ...TS>
struct variant_t
{
// other definition....
variant_t(variant_t<TS...>&& other)
{
// TODO, check if other is movable.
if (other.type_ == 0) return;
move_[other.type_ - 1](other.data_, data_);
type_ = other.type_;
}
variant_t(const variant_t<TS...>& other)
{
// TODO, check if other is copyable.
if (other.type_ == 0) return;
copy_[other.type_ - 1](other.data_, data_);
type_ = other.type_;
}
};
C++17的支持
variant类目前已经加入到c++17标准中,#include <variant>头文件即可使用std::variant
variant的应用
函数不确定返回值
using Two = std::pair<double, double>;
using Roots = std::variant<Two, double, void*>;
Roots FindRoots(double a, double b, double c)
{
auto d = b*b-4*a*c;
if (d > 0.0)
{
auto p = sqrt(d);
return std::make_pair((-b + p) / 2 * a, (-b - p) / 2 * a);
}
else if (d == 0.0)
return (-1*b)/(2*a);
return nullptr;
}
struct RootPrinterVisitor
{
void operator()(const Two& roots) const
{
std::cout << "2 roots: " << roots.first << " " << roots.second << '\n';
}
void operator()(double root) const
{
std::cout << "1 root: " << root << '\n';
}
void operator()(void *) const
{
std::cout << "No real roots found.\n";
}
};
void test()
{
std::visit(RootPrinterVisitor(), FindRoots(1, -2, 1)); //(x-1)*(x-1)=0
std::visit(RootPrinterVisitor(), FindRoots(1, -3, 2)); //(x-2)*(x-1)=0
std::visit(RootPrinterVisitor(), FindRoots(1, 0, 2)); //x*x - 2 = 0
}
无继承多态
struct Triangle {
void Draw() const {
std::cout << "△" << std::endl;
}
};
struct Circle {
void Draw() const {
std::cout << "○" << std::endl;
}
};
void test()
{
using Draw = std::variant<Triangle, Circle>;
Draw draw;
std::vector<Draw> draw_list {Triangle{}, Circle{}, Triangle{}};
auto DrawVisitor = [](const auto &t) { t.Draw(); };
for (const auto &item : draw_list) {
std::visit(DrawVisitor, item);
}
}