看 GoogleTest中的一段源码
这段代码是 gtest.h头文件中关于断言结果的抽象定义。
一般断言一个测试,我们要回答yes或no,而单测框架一般要求更多,比如,断言no的时候,需要告诉用户,为什么是no。
有鉴于此,作者封装了一个AssertionResult。
这个类很奇妙,的存在让你可以这样写用例
EXPECT_EQ(1, func(arg)) << "write some message";
源码:
class GTEST_API_ AssertionResult {
public:
// Copy constructor.
// Used in EXPECT_TRUE/FALSE(assertion_result).
AssertionResult(const AssertionResult& other);
// Used in the EXPECT_TRUE/FALSE(bool_expression).
explicit AssertionResult(bool success) : success_(success) {}
// Returns true iff the assertion succeeded.
operator bool() const { return success_; } // NOLINT
// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE.
AssertionResult operator!() const;
// Returns the text streamed into this AssertionResult. Test assertions
// use it when they fail (i.e., the predicate's outcome doesn't match the
// assertion's expectation). When nothing has been streamed into the
// object, returns an empty string.
const char* message() const {
return message_.get() != NULL ? message_->c_str() : "";
}
// TODO(vladl@google.com): Remove this after making sure no clients use it.
// Deprecated; please use message() instead.
const char* failure_message() const { return message(); }
// Streams a custom failure message into this object.
template <typename T> AssertionResult& operator<<(const T& value) {
AppendMessage(Message() << value);
return *this;
}
// Allows streaming basic output manipulators such as endl or flush into
// this object.
AssertionResult& operator<<(
::std::ostream& (*basic_manipulator)(::std::ostream& stream)) {
AppendMessage(Message() << basic_manipulator);
return *this;
}
private:
// Appends the contents of message to message_.
void AppendMessage(const Message& a_message) {
if (message_.get() == NULL)
message_.reset(new ::std::string);
message_->append(a_message.GetString().c_str());
}
// Stores result of the assertion predicate.
bool success_;
// Stores the message describing the condition in case the expectation
// construct is not satisfied with the predicate's outcome.
// Referenced via a pointer to avoid taking too much stack frame space
// with test assertions.
internal::scoped_ptr< ::std::string> message_;
GTEST_DISALLOW_ASSIGN_(AssertionResult);
};
隐式转换和仿函数
C++的基本数据类型有自己的隐式转换,继承自C,但是自定义的类型的隐式转换则颇为微妙。
int b = 3.14; // 编译器警告会丢失数据
double x = b; //x = 3.0 ok 装得下
int c = 'a'; //what c is ?
关于类型转换,C++提供了四种语法来实现显式的类型
static_cast<new_type>(origin_type); //例如 static_cast<int>(3.14);
const_cast<new_type>(origin_type); //转换掉变量的const或volatile属性,什么是volatile?
着重说一下 dynamic_cast<new_type>(origin_type);
//哈哈,这个转换最经常在一个继承树中看到,它是一个动态类型识别,我们知道面向对象继承关系里,基类的都是比较“小”的,派生类常常有更多的信息,更大一点,如果我们尝试将派生类转换为基类,一般来说把多出来的切掉就行了。
反过来就不行了。因为我们不知道基类要增加一些什么。
但是如果只是类型信息,C++可以提供一个dynamic_cast给我们判断一个派生类是否在继承树内。
dynamic_cast<target_type>(origin_type); 如果 origin_type可以转换成target_type, 它会返回
例如下面的例子:
class Base {
public:
Base();
virtual void action();
private:
};
class DeriveClass: public Base {
public:
void action
};
//main
int main(int argc, char *argv[]) {
Base *p = new DeriveClass();
// after some code line
// 你想知道指针p是不是DeriveClass类型
if (dynamic_cast<DeriveClass*>(p) != nullter) {
// yes
}
}
- dynamic_cast只能操作指针和引用,为什么?尝试操作非指针或引用的类型会报错。
- 如果A是AA的父类,AA继承自A,并且有虚函数,dynamic_cast<A*>(AA)才可能成功
- 使用时常常要测一下结果是否是nullptr
type obj = dynamic_cast<type>(expression); if (obj != nullptr) { // operator ponter obj }
在《More Effective C++》 的条款M2中 : 尽量使用C++风格的类型转换,理由是这些标号非常醒目,当你在阅读代码时,非常容易明确转换的含义;
另外,C++的转换方式,没有C那么奔放,一定程度上对放纵的C转换有所限制——程序员需要明确自己在做什么的时候,进行类型转换。
隐式类型转换
对于基本类型,转换是自然的,然而,用户自定义类型。C++提供的方式则看上去有点奇怪。
有两种方式:单参数的隐式构造函数和隐式转换符。
隐式转换符
在类中定义一种形如 operator type() 的函数,看起来很奇特,但是它是合法的。
这种函数没有返回类型(准确的说,不是没有,而是函数声明无须特别声明,其函数名就是它的返回类型,函数体的实现必须返回一个type的对象)。
举个例子
operator double() const {
}
这个声明能使你将类型转换成 double类型
double d = UserType(arg1, arg2);// 发生 operator double()的转换
另一种是单参数构造
例如,假设有一个类User,用数字age表示其年龄,内部重载 ‘==’运算,
然后,
运行这样的代码
if (User(4) == 10) { //毫无意义
std::cout << "yes" << endl;
}
看上去User(4)和10的比较没有什么意义,但是它还是能通过编译,跟踪输出如下
Output:
Call User constructor10
Call User constructor4
我们可以判断, User(4) == 10 发生了以下的事情,等价 User(4) == static_cast<User(10)>(10);
- 调用 构造函数User,参数为4
- 调用 构造函数User,参数为10
- 调用 函数 bool operator == (const User &other)
class User
{
public:
User(int age) {
_age = age;
std::cout << "Call User constructor" << age<< std::endl; //trace console
}
bool operator == (const User &other) {
return _age == other.age;
}
private:
int _number;
};
这是单参数构造函数会发生隐式转换的原因。
有时候使我们预想的,有时候不是。
因此为了避免此类问题,在《More Effective C++》 ITEM M5中,Scott Mayers也举了一个例子,因为编译器的灵活转型,导致失去在编译阶段发现错误的机会。
因此,一般我们定义单参数构造函数时,要格外考虑: 你是否需要隐式转换。如果不需要,就需要禁止它。
How 怎么防止隐式转换
对于单参数构造函数,在构造函数的签名声明explicit就可以
而 operator type() const 这种,我们尽量不要写它。除非真的希望如此。
GoogleC++编程规范对explicit的说明:
单参数构造函数使用C++关键字explicit。
定义: 通常,只有一个参数的构造函数可被用于转换( conversion,译者注:主要指隐式
转换,下文可见),例如,定义了Foo::Foo(string name),当向需要传入一个Foo对象
的函数传入一个字符串时,构造函数Foo::Foo(string name)被调用并将该字符串转换为
一个Foo临时对象传给调用函数。看上去很方便,但如果你并不希望如此通过转换生成一
个新对象的话,麻烦也随之而来。为避免构造函数被调用造成隐式转换,可以将其声明为
优点:避免不合时宜的变换。
缺点: 无
一般来说,任何时候我们都不要忘记为单参数构造函数加上explicit,单参数是指,除去所有缺省参数,只有一个参数。
std::scoped_ptr
AssertionResult中的message_是一个指向std::string的指针
这个智能指针是框架内部自己实现的 scoped_ptr。
我们来看看它的实现
template <typename T>
class scoped_ptr {
public:
typedef T element_type;
explicit scoped_ptr(T* p = NULL) : ptr_(p) {}
~scoped_ptr() { reset(); }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const { return ptr_; }
T* release() {
T* const ptr = ptr_;
ptr_ = NULL;
return ptr;
}
void reset(T* p = NULL) {
if (p != ptr_) {
if (IsTrue(sizeof(T) > 0)) { // Makes sure T is a complete type.
delete ptr_;
}
ptr_ = p;
}
}
private:
T* ptr_;
GTEST_DISALLOW_COPY_AND_ASSIGN_(scoped_ptr);
};
scoped_ptr禁止了拷贝和赋值构造函数,意味着你不可以像下面那样用它,它和C98的auto_ptr不一样,虽然auto_ptr也有类似的reset方法,但是auto_ptr的使用自由得多,因而也比较危险。
reset()默认参数是nullptr, 默认将封装的裸指针内存释放。传参时可以重置到另一块内存中,将原来的内存丢弃销毁。
release的作用是交出内存的控制权,一旦调用,智能指针不再对内存有所有权。
scoped_ptr<int> p(new int(5);
p = new int(100); //no
功能上看,颇像标准库的std::unique_ptr 但不是完全一样。后者的功能更强一些。
不知道为什么不使用标准库的智能指针。
网友评论