美文网首页
从源码中学语言:仿函数和隐式转换

从源码中学语言:仿函数和隐式转换

作者: 东方胖 | 来源:发表于2021-12-10 20:21 被阅读0次

看 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 但不是完全一样。后者的功能更强一些。
不知道为什么不使用标准库的智能指针。

相关文章

  • 从源码中学语言:仿函数和隐式转换

    看 GoogleTest中的一段源码这段代码是 gtest.h头文件中关于断言结果的抽象定义。一般断言一个测试,我...

  • Scala 隐式转换

    一、隐式转换 隐式转换需要执行隐式函数,隐式函数是以 implicit 关键字声明的带有单个参数的函数。隐式函数会...

  • scala implicit 隐式转换和隐式参数

    1.什么是隐式转换和隐式参数?隐式转换是以implicit 声明的带有单个参数的函数隐式参数是函数或方法带有一个标...

  • Go 语言基础——变量常量的定义

    go语言不支持隐式类型转换,别名和原有类型也不能进行隐式类型转换go语言不支持隐式转换 变量 变量声明 声明变量不...

  • Scala基础——隐式转换

    隐式转换 Scala的隐式转换,其实最核心的就是定义隐式转换函数,即implicitconversion func...

  • Scala implicit 隐式转换安全驾驶指南

    这篇短文将结合实例对隐式转换的各种场景进行解释和总结,希望看完的人能够安全驶过隐式转换这个大坑。 隐式转换函数 隐...

  • 03-数据类型转换

    数值类型转换 C语言中存在显式转换和隐式转换 Go语言中只有显式转换 Go语言中数值类型转换注意点 数值类型转换为...

  • C++中隐式类型转换

    1 operator隐式类型转换 1.1 std::ref源码中reference_wrapper隐式类型转换 在...

  • javascript数据类型隐式转换

    javascript数据类型隐式转换 一、函数类 isNaN()改函数会对参数进行隐式的Number()转换,如果...

  • 类型转换

    js的类型转换分成显示和隐式,显式转换常用类型转换函数进行转换,隐式最多用在条件判断,通常是把字符转为布尔型。类型...

网友评论

      本文标题:从源码中学语言:仿函数和隐式转换

      本文链接:https://www.haomeiwen.com/subject/sggtfrtx.html