發現大師們的錯誤
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);
相關文章
- 10 大常見的web開發錯誤Web
- (網頁)Java程式設計師們最常犯的10個錯誤(轉)網頁Java程式設計師
- 我們是如何解決偶發性的502錯誤的
- 新產品開發模式的9大錯誤模式
- 我們正在錯誤的組織程式碼!
- 工程師犯的最大錯誤?工程師
- 測試驅動開發上的五大錯誤
- 偉大的女程式設計師們程式設計師
- 我們身邊偉大的女程式設計師們程式設計師
- 引發網頁佈局災難的7個大錯誤網頁
- 大資料,還是大錯誤?大資料
- 從錯誤的RAID5中發現的問題AI
- 我們對人工智慧的10大誤解人工智慧
- JavaScript十大錯誤JavaScript
- 開發中遇到的錯誤
- 人們對Python在企業級開發中的10大誤解Python
- 程式設計師不應該再犯的五大程式設計錯誤程式設計師
- GetDlgItem() 出現錯誤Git
- java開發管理者們常犯之錯誤與解決辦法Java
- postfix 發信錯誤
- Flutter 最常出現的典型錯誤Flutter
- 配置nagios出現的錯誤iOS
- PbootCMS錯誤提示:執行SQL發生錯誤!錯誤:no such column: def1bootSQL
- 埠占用出現的不同的錯誤:
- JPA 開發中遇到的錯誤
- 為啥大神們可以根據ORA 600錯誤引數猜測 錯誤型別?型別
- Opencv出現detecMultiScale錯誤OpenCV
- [譯] Pull request review 的十大錯誤View
- 關於系統效能的10大錯誤
- Java程式設計師常犯的10個錯誤Java程式設計師
- 程式設計師可能常犯的 6 個錯誤程式設計師
- Python程式設計師的常見錯誤Python程式設計師
- 程式設計師招聘的7大誤區程式設計師
- 【譯】自動發現 .NET 5 中程式碼的潛在錯誤
- 我們在設計iPhone應用時犯過的錯誤iPhone
- IDEA發生“Error:java: 錯誤: 不支援發行版本 5”錯誤的解決方案IdeaErrorJava
- 執行SQL發生錯誤!錯誤:disk I/O errorSQLError
- 發現幾個大師的網站的RSS都不OK了。網站