effective c++ 27 尽量少做转型动作

程序员小x大约 4 分钟C++C++effective c++读书笔记

effective c++ 27 尽量少做转型动作

本节主要讨论强制类型转换的相关问题,并指出平常开发中尽可能少用强制类型转换,这不利于运行效率,有时候还可能引起意想不到的问题。

分析

在c语言中,强制类型转换格式是像下面这样的:

(T)expression //将expression转型为T

在c++中, 提供了四种新型的转型:

const_cast<T>(expression)

dynamic_cast<T>(expression)

reinterpret_cast<T>(expression)

static_cast<T>(expression)

我们通过两个案例来分析为什么尽量少做转型动作。

第一个案例是转型动作使用不当带来了问题

在SpecialWindow的virtual函数onResize内部,我们想先调用Window类内部的默认实现onResize

因此这里使用了static_cast<Window>(*this).onResize();去做转型,转型成Window类再调用onResize方法,这种办法能生效吗?

#include <iostream>
class Window
{
public:
	virtual void onResize() {
        a_ = 1;
    } 
	virtual ~Window() {}
    int getA() const
    {
        return a_;
    }
private:
    int a_{0};
};

class SpecialWindow : public Window
{
public:
	virtual void onResize()
	{
		static_cast<Window>(*this).onResize();
	}
private:
    int b_{0};
};

int main()
{
	SpecialWindow specialWindow;
	specialWindow.onResize();
    std::cout << specialWindow.getA() << std::endl;
}

输出:

0

have a tryopen in new window

从输出结果我们看出转型之后虽然调用到了父类的onResize方法,但是内部去修改a_的值却没有生效,因为我们最后打印specialWindow的getA方法发现a_的值还是0,并不是1。

问题出在了哪里?

static_cast<Window>(*this).onResize()这句话操作的并不是*this本身,而是一个副本。

static_cast<Window>(*this)是一个Window的临时副本。

上面的语句就有点像下面的语句,其最后修改的是临时对象中的值,而不是(*this)的。

Window tmp = static_cast<Window>(*this);
tmp.onResize();

修改起来也很简单,就是去掉强制类型转换,使用Window::onResize

#include <iostream>
class Window
{
public:
	virtual void onResize() {
        a_ = 1;
    } 
	virtual ~Window() {}
    int getA() const
    {
        return a_;
    }
private:
    int a_{0};
};

class SpecialWindow : public Window
{
public:
	virtual void onResize()
	{
		Window::onResize();
	}
private:
    int b_{0};
};

int main()
{
	SpecialWindow specialWindow;
	specialWindow.onResize();
    std::cout << specialWindow.getA() << std::endl;
}

下面再看第二个案例, 第二个案例是转型操作会带来额外的性能开销。

这里我们在vector中存放了父类的指针,但是我们想调用子类的blink方法,这里便需要使用dynamic_cast强转为子类对象的指针,再调用blink方法。

#include <iostream>
#include <memory>
#include <vector>
class Window
{
public:
    virtual ~Window() = default;
};

class SpecialWindow : public Window
{
public:
	void blink()
	{
        std::cout << "blink" << std::endl;
		int x = 7;
	}
};

int main()
{
    using VPW = std::vector<std::shared_ptr<Window>>;
    VPW winPtrs;
    std::shared_ptr<Window> ptr1 = std::make_shared<SpecialWindow>();
    std::shared_ptr<Window> ptr2 = std::make_shared<SpecialWindow>();  
    winPtrs.push_back(ptr1);
    winPtrs.push_back(ptr2);
    for(VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
    {
        Window* w = iter->get();
        SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get());
        if(psw)
            psw->blink();
    }     
}

我们知道dynamic_cast是有性能开销的,dynamic_cast内部需要调用strcmp进行字符串比较。如果在一个四层深的单继承体系内的某个对象身上执行dynamic_cast,就可能耗用多达四次的strcmp调用。

那么该如何进行优化呢?

一种优化的方法就是vector内部就不要存Window类型的share_ptr,而是存储SpecialWindow类型的share_ptr。

#include <iostream>
#include <memory>
#include <vector>
class Window
{
public:
    virtual ~Window() = default;
};

class SpecialWindow : public Window
{
public:
	void blink()
	{
        std::cout << "blink" << std::endl;
		int x = 7;
	}
};

int main()
{
    using VPW = std::vector<std::shared_ptr<SpecialWindow>>;
    VPW winPtrs;
    std::shared_ptr<SpecialWindow> ptr1 = std::make_shared<SpecialWindow>();
    std::shared_ptr<SpecialWindow> ptr2 = std::make_shared<SpecialWindow>();  
    winPtrs.push_back(ptr1);
    winPtrs.push_back(ptr2);
    for(VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
    {
        (*iter)->blink();
    }     
}

另一种方法就是将blink修改为virtual的函数:

#include <iostream>
#include <memory>
#include <vector>
class Window
{
public:
    virtual ~Window() = default;
    virtual void blink(){};
};

class SpecialWindow : public Window
{
public:
	virtual void blink() override
	{
        std::cout << "blink" << std::endl;
		int x = 7;
	}
};

int main()
{
    using VPW = std::vector<std::shared_ptr<Window>>;
    VPW winPtrs;
    std::shared_ptr<Window> ptr1 = std::make_shared<SpecialWindow>();
    std::shared_ptr<Window> ptr2 = std::make_shared<SpecialWindow>();  
    winPtrs.push_back(ptr1);
    winPtrs.push_back(ptr2);
    for(VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
    {
        (*iter)->blink();
    }     
}

当然上述两种办法只是两种思路,并不意味着必须这样做。当确实需要强转时,就去强转。

一句话总结就是尽可能少的去转型,但并不意味着完全杜绝转型,这一点要牢记,需要的时候该转还得转。

总结

  • 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个别设计需要转型动作,试着发展无需转型的替代设计。
  • 如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进他们自己的代码内。
  • 宁可使用c++style新式转型,不要使用旧式转型,前者很容易辨识出来,而且也比较有着分门别类的职掌。
Loading...