C++对话系列
1 auto_ptr再回忆
2 空引用
3 起源
4 谁的代码更易于移植?
5 通过任何其他名字
6 Virtually Yours
7 方形尖塔
8 访问限制
9 重定向
10 关于操纵器的对话
11 根源
12 Abstract Factory, Template Style
13 怎样持久化一个对象
14 The Bind That Ties
15 Back to Base-ics
16 Al-Go-Rithms
17 匈牙利命名
18 我为你准备一切
19 新的起点,第一部分
20 新的起点,第二部分
21 模板特化,缺省参数和其他一些有趣的事情
22 想睡觉,偶然
23 产生真正的 hash对象
24 好的,坏的,不赞成的
25 Getting to the Point
26 A Midsummer Night’s Madness
27 Baseless Exceptions
28 Contracts, Promises, and Mere Semantics
30 It's an Object-ful Lifetime
另 与大虾对话 : 领悟设计模式
auto_ptr再回忆
auto_ptr是一个有用的工具,但是就象你刚才发现的那样,它不是万能的。好好琢磨Josuttis chapter 4.[3],永远不要在标准程序库的容器中用auto_ptr,如vector<auto_ptr>,因为auto_ptr的拷贝和赋值不能达到标准的要求。此外,永远不要用auto_ptr指向对象数组,因为auto_ptr的析构函数用non-array delete删除所拥有的对象;对于对象数组来说,可以用一个vector。
空引用
使用引用代替指针的主要的理由之一就是把你从不得不测试它是否引用一个无效的目标的负担中解放出来。空引用的唯一来源就是提领一个空指针——这在神圣的标准里明确被禁止,在未定义的行为(Undefined Behavior)里也写的很清楚。
谁的代码更易于移植?
通过任何其他名字
“这个类实施的是Poorly Named模式,或者说PNP。也被称作Visitor模式[1]。”
“呃,嗯。”我现在明白了一些。“我曾经读到过Visitor模式。但那只是针对能够互相迭代访问的对象,不是么?”
她叹了口气。“一个常见的误解。考虑一下V的含义,不是Visitor而是Virtual。”她解释道,“PNP最有用的地方,就是在不会进一步改变类体系的情况下增加虚拟函数。首先注意Accept在Personnel类和子类中是如何实现的。”
Virtually Yours
模板方法模式(Template Method pattern):公共、非虚拟成员函数显然将调用受保护的虚拟函数
存取权限和虚拟性是互相独立的,决定一个函数是静态绑定还是动态绑定取决于这个函数最后是如何被调用的。你
重定向
#include <iostream>
#include <fstream>
int main()
{
std::ofstream logFile("out.txt");
std::streambuf *outbuf = std::cout.rdbuf(logFile.rdbuf());
std::streambuf *errbuf = std::cerr.rdbuf(logFile.rdbuf());
// do the actual work of the program;
// GUI code and event loop would go here
std::cout << "This would normally go to cout but goes to the log file/n";
std::cerr << "This would normally go to cerr but goes to the log file /n";
logFile << "This goes to the log file/n";
// end of program body
// restore the buffers
std::cout.rdbuf(outbuf);
std::cerr.rdbuf(errbuf);
}
交换两个缓冲区
basic_ios::rdbuf函数并不是虚拟函数。ofstream只提供了一个rdbuf函数,此函数没有参数,返回指向对象内部的文件缓冲流。也就是说std::ofstream::rdbuf(void) 隐藏了 std::ostream::rdbuf(std::streambuf *)。
“你可以通过一个指向std::ostream的指针操纵你的log文件,如下:”
int main()
{
std::ofstream logFile("err.log");
std::ostream * baseManipulator = &logFile;
baseManipulator->rdbuf(std::cerr.rdbuf(baseManipulator->rdbuf()));
}
The Bind That Ties 把bind1st和bind2nd简称为bind函数,这样你就可以把二元函数对象当作一元函数对象来使用
#include <vector>
#include <iostream>
#include <functional>
#include <iterator>
#include <algorithm>
void doSomething(int i, const char *c);
// 我的第一个二元功能函数,
// 首先,我假定doSomething是某个库函数,
// 我并没有它的源代码。
// 关于可移植性:MS VC6.0不喜欢在模板的返回类型中使用void,
// 所以在MS VC6.0中对operator( )稍作修改,使它返回一个类型(如true)
struct doSomethingWrapper : public
std::binary_function<int, const char *, void>
{
void operator()(int iValue, const char *cValue) const
{
doSomething(iValue, cValue);
}
};
// 现在,就建立了一个内部的功能函数。
// 关于可移植性,同上。
struct doSomethingDirect : public
std::binary_function<int, const char *, void>
{
void operator()(int iValue, const char *cValue) const
{
std::cout << cValue
<< " "
<< iValue
<< ". " ;
}
};
// 这是个帮助器模板,因为我比较懒,它能减少打字量。
template <class Collection, class Function>
Function for_all(Collection &c, const Function &f)
{
return std::for_each(c.begin(), c.end(), f);
}
int main()
{
// 首先,建立vector。
std::vector<int> vect;
for (int i=0; i<10; ++i) {
vect.push_back(i);
}
for_all(vect, std::bind2nd(doSomethingWrapper(), "Wrapper:"));
std::cout << "/n";
for_all(vect, std::bind2nd(doSomethingDirect(), "Direct:"));
system("PAUSE");
return 0;
}
// 我独树一帜的第三方库函数
void doSomething(int i, const char *c)
{
std::cout << c << " " << i << ". " ;
}
Al-Go-Rithms
神圣的C标准明确指出,strtok是不可再入的(事实上没有一个标准C函数能够做到这点)。更糟的是,在你这个情况,strtok在各级调用中都保持全局状态,考虑一下你的函数所运行的上下文环境,它在另一个解析函数的内部使用,而该解析函数也可能使用strtok。
template<class T>
class StringTok
{
public:
StringTok( const T& seq,
typename T::size_type pos = 0 )
: seq_( seq ) , pos_( pos ) { }
T operator()( const T& delim );
private:
const T& seq_;
typename T::size_type pos_;
};
template<class T>
T StringTok<T>::operator()( const T& delim )
{
T token;
if( pos_ != T::npos )
{
// 开始寻找标志
typename T::size_type first =seq_.find_first_not_of( delim.c_str(), pos_ );
if( first != T::npos )
{
// 所找到标志的长度
typename T::size_type num =seq_.find_first_of( delim.c_str(), first ) - first;
// 把所有的工作放在此处
token = seq_.substr( first, num );
//完成,现在提交只采用不抛出异常的操作
pos_ = first+num;
if( pos_ != T::npos ) ++pos_;
if( pos_ >= seq_.size() ) pos_ = T::npos;
}
}
return token;
}
我为你准备一切[1]
Case1:
#include "stdafx.h"
#include <iostream>
#include <boost/any.hpp>
//using boost::any_cast;
const char * initialMessage = "Snowflakes keep falling on my head";
int _tmain(int argc, _TCHAR* argv[])
{
boost::any multiA(42L);
boost::any multiB(initialMessage);
multiA = multiB;
std::cout <<boost::any_cast<const char *>(multiA) << std::endl;
system("PAUSE");
return 0;
}
先知Henney建议把属性作为any类的潜在用途之一[2]。因为any类可以保存任何东西,可以用于各种场合。这将是实现属性的好方法啊。只要创建一个struct,让它拥有一个std::string来表示属性名,一个any类来表示属性值
any类的另一种可能的用途是作为基于多态的回调函数的参数。如果我没记错的话,Henney给出的例子是这样的。
http://www.parashift.com/c++-faq-lite/
模板特化,缺省参数和其他一些有趣的事情
在产品代码中, 当你想用关联容器(associative container)时, 你几乎总是需要以hash为基础的(hash-based)容器, 而不是以树为基础(tree-based)的. 注意hash_map不是标准的, 但是在我们使用的所有平台上都可用 [1].当然你必须改变包含的头文件的名字。同时你还应当检查一下缺省的hash函数是否适合你的数据,如果不合适,你自己定制一个。测试、计时,如果可行的话就大胆采纳。
Getting to the Point
Boost库中的灵巧指针
“共有五种灵巧指针,”Guru一边坐下一边说着。”两个处理指针的单所有权,两个处理共享所有权。”
“共享所有权?哦,类似于带引用计数的类?”
灵巧指针是成对的,一个处理指向单一物体的指针,另一个处理指向数组的指针。”在说的时候,她在白板上写下:scoped_ptr,scoped_array,shared_ptr和 shared_array。
“下的一个叫weak_ptr。它是shared_ptr的非拥有关系的观察者。
scoped_ptr和scoped_array不可拷贝
auto_ptr仍然有它的用处。不像auto_ptr,scoped_ptr 没有release()成员-它不能够放弃指针的所有权。 因为这个,以及因为scoped_ptr不能够被复制,当scoped_ptr离开生存范围时,它所管理的指针总是被delete。这使得scoped_ptr不适用于需要传递所有权的地方,比如厂。
使用scoped_ptr向其他程序员表明你的意图。它告诉其他人,‘这个指针不应该在当前范围外被复制’。正相反,auto_ptr允许从产生指针的代码空间向外传递所有权,但是它维持对指针的控制除非所有权的传递是完全地。 这当然对写异常安全的代码有重要意义。
scoped_array的行为和scoped_ptr相同,除了它处理是对象数组而不是单个对象。 scoped_array 提供了稍有差别的访问函数。它没有operator*或operator->,但它有operator[]。
shared_*形的灵巧指针是非常有用的工具。 他们是引用计数型的指针,能够区分出所有者和观察者。
shared_*的灵巧指针可以用一个指向不完全类型的指针来实例化,但必须要指明一个函数或函数子供销毁被拥有对象时调用。
因为 shared_ 指针被设计可被拷贝,它们完全适用于标准容器, 包括associative 容器。并且,在那些必须强制类型转换的特别场合上,可以定义特别的类型转换操作以生成新的shared_*形的灵巧指针。
shared_ptr<Base>sharedBase( new Derived );
shared_ptr<Derived> sharedDer =
shared_dynamic_cast<Derived>( sharedBase);
shared_ptr<Unrelated> sharedUnrelated =
shared_dynamic_cast<Unrelated>( sharedBase);
A Midsummer Night’s Madness
在不改变申明的含义的前提下,将const关键字放在尽可能右边。
我喜欢申明'const int * iptr'
用编译器使用的方式来读它,你必须这么做。你其实正在的说是'iptr是一个指向const的int的指针', 从右向到左才是'int const * iptr;'的真实含义。
Baseless Exceptions
记住,徒弟,绝不要假设你或编译器中的任何一个是正确的。总是查神圣的标准来确定你的理解和智慧。
#include <iostream>
#include <stdlib.h>
using namespace std;
class Base
{
public:
virtual ~Base(){}
};
class DerivedPrivately : private Base
{
// ...
//whatever ...
};
class DerivedPublicly : public Base
{
// ...
//whatever ...
};
void g(int which)
{
if (which < 0) throw DerivedPublicly();
else if (which == 0) throw Base();
else throw DerivedPrivately();
}
void f(int value)
{
try {
g(value);
}
catch(Base &)
{
cout << "Caught Base" << endl;
}
catch ( ... )
{
cout << "Caught something else" << endl;
}
}
int main()
{
f(-1);
f(0);
f(1);
system("PAUSE");
}
前两个调用的输出应该是'Caught Base',最后一个是'Caught something else'。编译器显然做了某种运行期检查,所以限定它为公有无二义基类
如果你修改私有派生出的类,加一句friend void f(int);语句。然后你预料会发生什么?”
嗯, DerivedPrivately对象已经申明f()为友元,所以我预料它打印出'Caught Base'。
Contracts, Promises, and Mere Semantics
当申明参数类型时,最好遵循传统的C++接口惯用法,除非它不合适:
如果实参需要被改变,传非const的指针或引用。
如果实参不被改变,传const的引用,或在拷贝开销很小时传值。
但是,徒弟,你必须注意'最好'和'除非'这两个词。这只是惯用法。不是教条。它不是没有例外的。
而同样事还发生在所有会转移所有权类-顺便提一句,其典型特征是在拷贝构造函数的形参上使用非const的引用。这就是你要的答案:传值通常意味着你不会碰源对象,除了auto_ptr,对它含义正相反-并且更差,因为不但修改了源 auto_ptr,还将它置为了NULL。而传指针常意味着准备修改源对象,除了auto_ptr,对它含义正相反-你正在避免修改它。对于auto_ptr,传统的惯用法正干着坏事。而这正是基于通常的惯用法的代码,例如容器对它们所包容的对象作的假设,不能和auto_ptr协同工作的原因。但不只是对于auto_ptr;对所有在拷贝时进行所有权传递的类都是这样。总要特别留心在拷贝构造函数中的非const引用参数;那就是你通常会遗漏的致命之处。
It's an Object-ful Lifetime
lvalue和rvalue。神圣的标准告诉我们T()形式的一个显式类型转换产生一个rvalue。 rvalue只能绑定到一个const的引用,而lvalue没有这个限制。另一方面,返回引用的函数,其结果是得到一个lvalue[1]. 因此编译器能将非const的引用绑定在ref()的结果上。
不寻常的技巧是危险的,不应该轻易使用。 的确,我认为至少有一问题,关于对象生命期的,会导致未定义行为