返回值优化和noncopyable class

转载自:http://www.708luo.com/?p=22

今天上班,遇到件比较有意思的事情。有同事叫我看一段代码,这段代码在gcc上可以编译通过,但是用bullseye cover编译用于统计覆盖率的时候,链接阶段报错了。

我简化一下这段代码如下:

class Integer 

{

public:

    Integer(int x=0):_x(x){}

    Integer operator+(const Integer& rhs)

    {

        Integer tmp;

        tmp._x += rhs._x; 

        return tmp;

    }

    ~Integer(){}

private:

    Integer(const Integer&);

    int _x; 

};

我看了一下bullseye cover链接阶段报的错误,其实就是说找不到"Integer(const Integer&)"函数的定义,而这个函数在"Integer operator+(const Integer& rhs)"中调用了。

 

我一看,bullseye cover的行为是对的,operator+中函数返回的是对象,而非引用,理应有次拷贝构造的操作。

那为什么gcc可以链接通过呢?第一反应就是返回值优化,把这次拷贝构造的操作优化掉了。

写了段代码测试一下(代码我就不贴,就是定义个拷贝构造函数,打印点提示,然后调用一下operator+),果然是返回值优化的原因。而且竟然在优化选项-O0的就开启返回值优化了,这点倒有点让我意外

 

本来我还认为,NonCopyable class的拷贝构造函数只要声明为private就行了,至于定义不定义倒无所谓,反正依赖的是访问权限的控制。

这次经历之后才意识到,将NonCopyable class的拷贝构造函数定义为空是很危险的一件事。因为它也可能被成员函数来调用,这个时候访问权限也无能为力了,只能靠函数未定义来提示了。

 

至于对错,我觉得两个编译器的行为都是可接受的,要修改的是这种代码的编写方式:既是Noncopy class,成员函数又要返回对象实例。

不过gcc确实做得优秀,不仅仅优化消除了一些函数调用,还因为消除了一些不必要的函数调用让摇摇欲坠的代码编译通过了。

 

本来问题到这里也就结束了,我觉得在gcc中还会有个比较好玩的现象:

如果调用NonCopyable class拷贝构造是个非成员函数,那么编译阶段会提示访问权限的错误。但是假设编译可以通过,因为编译优化的原因,这个函数又不会被调用到,似乎有点“冤”。如果绕过权限访问控制,比如把那个非成员函数声明为NonCopyable class的友元函数,完全是可以编译通过。

如下代码:

class Integer 

{

public:

    friend Integer add(const Integer&,const Integer&);

    Integer(int x=0):_x(x){}

    ~Integer(){}

    int get()const{return _x;}

    void set(int x){_x=x;}

    Integer& operator=(const Integer& rhs)

    {

        this->_x = rhs._x; 

        return *this;

    }

private:

    Integer(const Integer&);

    int _x; 

};



Integer add(const Integer& lhs,const Integer& rhs)

{

    Integer tmp;

    tmp.set(lhs.get()+rhs.get());

    return tmp;

}

上面的代码,如果注释掉第4行,编译阶段就会有'Integer::Integer(const Integer&)' is private这样的错误。如果反注释第4行,编译、链接都是通过的。

其实上面这段代码,还没写全面,如果有地方调用了add函数,在那里也会报一个'Integer::Integer(const Integer&)' is private这样的错误,需要把调用处的代码也绕过权限控制。

我觉得如果能把代码优化操作放到检查代码访问权限操作之前,就可以避免这个问题了。不过对编译器底层实现不太了解,不知道这个调整是否可行。

或者我们在写NonCopyable class的时候,不是声明私有的拷贝构造函数,而是声明公有的拷贝构造函数,但是不定义它。

不知道这样会有什么弊端,除了错误提示会晚一点。但是这样有个好处是,前面说的“冤”的情况可以避免。

你可能感兴趣的:(Class)