发现大师们的错误

原创 2015年11月17日 16:40:17

Lippman 「C++ Primer」 P537

享誉世界的C++经典「C++ Primer」(第五版)在介绍子类的虚函数时,说一个派生类的函数如果覆盖了(或者说试图重写,override)某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。这话当然没问题,紧接着他说,同样,派生类中虚函数的返回类型也必须与基类函数匹配。该规则存在一个例外,当类的虚函数返回类型是类本身的指针或引用时,上述规则有效。

问题恰出现在后半句,这句话是对协变返回类型(covariant return type)的刻板理解和窄化,关于协变返回类型,更详细的内容请见 C++基础::语法特性::函数重写(override)与协变返回类型(covariant return type)

协变返回类型要求子类重写的虚函数的返回值(指针或引用类型)与父类被重写的虚函数之间构成继承关系,并不要求返回类本身的指针或引用,

#include <iostream>
class A{};
class B: public A{};

class C
{
public:
    virtual A* foo()
    {
        std::cout << "C::foo()" << std::endl;
        return new A;
    }
};

class D: public C
{
public:
    virtual B* foo()
    {
        std::cout << "D::foo()" << std::endl;
        return new B;
    }
};

int main(int, char**)
{
    C c; D d;
    c.foo();
    d.foo();
    return 0;
}

并不要求构成重载的虚函数之间,它们的返回值是自身的指针或者引用。

控制台输出为:

C::foo()
D::foo()

编译通过,运行通过!

当然,我这么做是吹毛求疵、咬文嚼字的表现,这么做并无十分明确的意义,只是大师们在这里并未言明协变返回类型,其实是对协变返回类型的窄化和呆板认识。

侯捷STL源码剖析——for_each与transform

在介绍for_each算法的源码时,

template<typename InputIterator, typename Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
    for(; first != last; ++first)
        f(*first);
    return f;
}

侯捷老师说,“将仿函数f施行于[first, last)区间内的每一个元素身上,f不可以改变元素内容,因为first和last都是InputIterator”。侯捷老师接着又说,“如果想要一一修改元素内容,应该使用算法transform”,言下之意就是for_each与transform的不同在于前者不对元素内容进行修改,而后者进行了修改。可是如果for_each没修改元素内容的话,函数返回的是仿函数,那么函数的目的是什么呢?
而且在实际中:

class Item
{
private:
    std::string _name;
    float _price;
public:
    Item(const std::string& name, float price):_name(name), _price(price){}
    std::string getName() const { return _name;}
    void setName(const std::string& name) { _name = name;}
    float getPrice() const { return _price;}
    void setPrice(float price) { _price = price;}
};

int main(int, char**)
{
    tyepdef std::shared_ptr<Item> ItemPtr;
    std::vector<ItemPtr> books {ItemPtr(new Item("C++", 10.)), ItemPtr(new Item("Python", 20.)), ItemPtr(new Item("Machine Learning", 30.))};


    // 这时如果我们想商品的价格在原价的基础上再加5,使用for_each算法
    std::for_each(books.begin(), books.end(), [](ItemPtr& elem){elem->setPrice(elem->getPrice()+5)});
                    // 第一:传递给lambda函数的实参是一个引用类型,
                    // 第二:对原有的元素内容进行了修改
    for (const auto& elem: books)
        std::cout << elem->getName() << ": " << elem->getPrice() << std::endl;

    return 0;
}

// 我们再来看transform的做法
int main(int, char**)
{
    tyepdef std::shared_ptr<Item> ItemPtr;
    std::vector<ItemPtr> books {ItemPtr(new Item("C++", 10.)), ItemPtr(new Item("Python", 20.)), ItemPtr(new Item("Machine Learning", 30.))};

    std::vector<ItemPtr> books2(books.size());
    std::transform(books.begin(), books.end(), books2.begin(), 
                    [](ItemPtr elem){ return ItemPtr(new(elem->getName(), elem->getPrice()+5));});
    for (const auto& elem: books)
        std::cout << elem->getName() << ": " << elem->getPrice() << std::endl;
    for (const auto& elem: books)
        std::cout << elem->getName() << ": " << elem->getPrice() << std::endl;
                    // transform所做的工作是,将仿函数施加输入序列后返回给输出序列
                    // 我们看到传递给transform的仿函数的参数是一个对象value语义,而非reference 
}

如上代码我们可以看到,真正对输入序列进行修改的不仅不是transform,而是for_each,因为for_each的输入是单独的一个序列,而transform的输入是两个输入序列,也即将仿函数施行在一个输入序列得到的结果再返回给另一个序列。而且,传递给for_each的仿函数对象的参数是引用类型(swap(int, int)没有意义),传递给transform的是value 语义。

在STL的语言环境范畴里,直接改变元素值(如for_each),或者复制元素到另一个区间的过程中改变元素值(如transform,原区间不发生变化)都属于更易型算法(modifying algorithm)。

我们可以继续探索二者的区别,因为是将操作的返回值赋予元素,而不是直接改动元素,transform的速度稍慢些,不过其灵活性更高,因为它可以把某个序列复制到标的序列(目标序列),同时改动元素内容。

我们再来看新标准下的for_each,for_each算法非常灵活,它允许以不同的方式访问(可以对元素不进行修改)、处理和修改每一区间内的元素,然而,自C++11起,for_each恐将日益丧失其重要性,因为十分方便和强大的range-based for循环:

// 同样是为每一个商品的价钱+5
for (auto& elem: books)
    elem->setPrice(elem->getPrice() + 5);
版权声明:本文为博主原创文章,未经博主允许不得转载。

WINDOWS游戏编程大师技巧-常见编绎连接错误FAQ

原文来源:http://st251256589.blog.163.com/blog/static/164876449201131101854589/ 1.无法从“const char [10]”...

苹果工业设计大师称竞争对手们目标错误

苹果工业设计高级副总裁乔纳森·伊维(Jonathan Ive)日前罕见地接受了英国媒体的采访,他指出苹果的竞争对手们都瞄准了错误的目标,同时他还介绍了苹果对此而采取的措施。   苹...
  • wwwgui
  • wwwgui
  • 2012年03月19日 18:15
  • 486

磁盘错误扫描修复大师

  • 2011年07月04日 10:16
  • 437KB
  • 下载

磁盘错误扫描修复大师

  • 2010年12月28日 16:14
  • 156KB
  • 下载

关于SCRIPT5022: QuotaExceededError错误,从发现到解决

今天同事报了一个线上bug,

MDF文件修复大师 823错误

  • 2016年11月21日 15:21
  • 5.16MB
  • 下载

磁盘错误扫描修复大师

  • 2010年12月28日 16:22
  • 1.28MB
  • 下载

由ORA-00979错误发现ORACLE一个BUG

由ORA-00979错误发现ORACLE一个BUG今天测试在执行一个sql语句的时候,报错ora-00979错误。SQL如下: select rownum, k.*  from (select A.t...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:发现大师们的错误
举报原因:
原因补充:

(最多只允许输入30个字)