青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

陳碩的Blog

C++ 工程實踐(8):值語義

陳碩 (giantchen_AT_gmail)
http://blog.csdn.net/Solstice  http://weibo.com/giantchen
陳碩關于 C++ 工程實踐的系列文章: http://blog.csdn.net/Solstice/category/802325.aspx
排版正常的版本: http://www.cnblogs.com/Solstice/category/287661.html
陳碩博客文章合集下載: http://blog.csdn.net/Solstice/archive/2011/02/24/6206154.aspx
本作品采用“Creative Commons 署名-非商業性使用-禁止演繹 3.0 Unported 許可協議(cc by-nc-nd)”進行許可。http://creativecommons.org/licenses/by-nc-nd/3.0/

本文是前一篇《C++ 工程實踐(7):iostream 的用途與局限》的后續,在這篇文章的“iostream 與標準庫其他組件的交互”一節,我簡單地提到iostream的對象和C++標準庫中的其他對象(主要是容器和string)具有不同的語義,主要體現在iostream不能拷貝或賦值。今天全面談一談我對這個問題的理解。

本文的“對象”定義較為寬泛,a region of memory that has a type,在這個定義下,int、double、bool 變量都是對象。

什么是值語義

值語義(value sematics)指的是對象的拷貝與原對象無關,就像拷貝 int 一樣。C++ 的內置類型(bool/int/double/char)都是值語義,標準庫里的 complex<> 、pair<>、vector<>、map<>、string 等等類型也都是值語意,拷貝之后就與原對象脫離關系。Java 語言的 primitive types 也是值語義。

與值語義對應的是“對象語義/object sematics”,或者叫做引用語義(reference sematics),由于“引用”一詞在 C++ 里有特殊含義,所以我在本文中使用“對象語義”這個術語。對象語義指的是面向對象意義下的對象,對象拷貝是禁止的。例如 muduo 里的 Thread 是對象語義,拷貝 Thread 是無意義的,也是被禁止的:因為 Thread 代表線程,拷貝一個 Thread 對象并不能讓系統增加一個一模一樣的線程。

同樣的道理,拷貝一個 Employee 對象是沒有意義的,一個雇員不會變成兩個雇員,他也不會領兩份薪水。拷貝 TcpConnection 對象也沒有意義,系統里邊只有一個 TCP 連接,拷貝 TcpConnection  對象不會讓我們擁有兩個連接。Printer 也是不能拷貝的,系統只連接了一個打印機,拷貝 Printer 并不能憑空增加打印機。凡此總總,面向對象意義下的“對象”是 non-copyable。

Java 里邊的 class 對象都是對象語義/引用語義。ArrayList<Integer> a = new ArrayList<Integer>(); ArrayList<Integer> b = a; 那么 a 和 b 指向的是同一個ArrayList 對象,修改 a 同時也會影響 b。

值語義與 immutable 無關。Java 有 value object 一說,按(PoEAA 486)的定義,它實際上是 immutable object,例如 String、Integer、BigInteger、joda.time.DateTime 等等(因為 Java 沒有辦法實現真正的值語義 class,只好用 immutable object 來模擬)。盡管 immutable object 有其自身的用處,但不是本文的主題。muduo 中的 Date、Timestamp 也都是 immutable 的。

C++中的值語義對象也可以是 mutable,比如 complex<>、pair<>、vector<>、map<>、string 都是可以修改的。muduo 的 InetAddress 和 Buffer 都具有值語義,它們都是可以修改的。

值語義的對象不一定是 POD,例如 string 就不是 POD,但它是值語義的。

值語義的對象不一定小,例如 vector<int> 的元素可多可少,但它始終是值語義的。當然,很多值語義的對象都是小的,例如complex<>、muduo::Date、muduo::Timestamp。

值語義與生命期

值語義的一個巨大好處是生命期管理很簡單,就跟 int 一樣——你不需要操心 int 的生命期。值語義的對象要么是 stack object,或者直接作為其他 object 的成員,因此我們不用擔心它的生命期(一個函數使用自己stack上的對象,一個成員函數使用自己的數據成員對象)。相反,對象語義的 object 由于不能拷貝,我們只能通過指針或引用來使用它。

一旦使用指針和引用來操作對象,那么就要擔心所指的對象是否已被釋放,這一度是 C++ 程序 bug 的一大來源。此外,由于 C++ 只能通過指針或引用來獲得多態性,那么在C++里從事基于繼承和多態的面向對象編程有其本質的困難——資源管理。

考慮一個簡單的對象建模——家長與子女:a Parent has a Child, a Child knows his/her Parent。在 Java 里邊很好寫,不用擔心內存泄漏,也不用擔心空懸指針:

public class Parent
{
    private Child myChild;
}
    
public class Child
{
    private Parent myParent;
}

只要正確初始化 myChild 和 myParent,那么 Java 程序員就不用擔心出現訪問錯誤。一個 handle 是否有效,只需要判斷其是否 non null。

在 C++ 里邊就要為資源管理費一番腦筋:Parent 和 Child 都代表的是真人,肯定是不能拷貝的,因此具有對象語義。Parent 是直接持有 Child 嗎?抑或 Parent 和 Child 通過指針互指?Child 的生命期由 Parent 控制嗎?如果還有 ParentClub 和 School 兩個 class,分別代表家長俱樂部和學校:ParentClub has many Parent(s),School has many Child(ren),那么如何保證它們始終持有有效的 Parent 對象和 Child 對象?何時才能安全地釋放 Parent 和 Child ?

直接但是易錯的寫法:

class Child;

class Parent : boost::noncopyable
{
 private:
  Child* myChild;
};

class Child : boost::noncopyable
{
 private:
  Parent* myParent;
};

如果直接使用指針作為成員,那么如何確保指針的有效性?如何防止出現空懸指針?Child 和 Parent 由誰負責釋放?在釋放某個 Parent 對象的時候,如何確保程序中沒有指向它的指針?在釋放某個 Child 對象的時候,如何確保程序中沒有指向它的指針?

這一系列問題一度是C++面向對象編程頭疼的問題,不過現在有了 smart pointer,我們可以借助 smart pointer 把對象語義轉換為值語義,從而輕松解決對象生命期:讓 Parent 持有 Child 的 smart pointer,同時讓 Child 持有 Parent 的 smart pointer,這樣始終引用對方的時候就不用擔心出現空懸指針。當然,其中一個 smart pointer 應該是 weak reference,否則會出現循環引用,導致內存泄漏。到底哪一個是 weak reference,則取決于具體應用場景。

如果 Parent 擁有 Child,Child 的生命期由其 Parent 控制,Child 的生命期小于 Parent,那么代碼就比較簡單:

class Parent;
class Child : boost::noncopyable
{
 public:
  explicit Child(Parent* myParent_)
    : myParent(myParent_)
  {
  }

 private:
  Parent* myParent;
};

class Parent : boost::noncopyable
{
 public:
  Parent()
    : myChild(new Child(this))
  {
  }

 private:
  boost::scoped_ptr<Child> myChild;
};

在上面這個設計中,Child 的指針不能泄露給外界,否則仍然有可能出現空懸指針。

如果 Parent 與 Child 的生命期相互獨立,就要麻煩一些:

class Parent;
typedef boost::shared_ptr<Parent> ParentPtr;

class Child : boost::noncopyable
{
 public:
  explicit Child(const ParentPtr& myParent_)
    : myParent(myParent_)
  {
  }

 private:
  boost::weak_ptr<Parent> myParent;
};
typedef boost::shared_ptr<Child> ChildPtr;


class Parent : public boost::enable_shared_from_this<Parent>,
               private boost::noncopyable
{
 public:
  Parent()
  {
  }

  void addChild()
  {
    myChild.reset(new Child(shared_from_this()));
  }

 private:
  ChildPtr myChild;
};

int main()
{
  ParentPtr p(new Parent);
  p->addChild();
}

上面這個 shared_ptr+weak_ptr 的做法似乎有點小題大做。

考慮一個稍微復雜一點的對象模型:a Child has parents: mom and dad; a Parent has one or more Child(ren); a Parent knows his/her spouser. 這個對象模型用 Java 表述一點都不復雜,垃圾收集會幫我們搞定對象生命期。

public class Parent
{
    private Parent mySpouser;
    private ArrayList<Child> myChildren;
}

public class Child
{
    private Parent myMom;
    private Parent myDad;
}

如果用 C++ 來實現,如何才能避免出現空懸指針,同時避免出現內存泄漏呢?借助 shared_ptr 把裸指針轉換為值語義,我們就不用擔心這兩個問題了:

class Parent;
typedef boost::shared_ptr<Parent> ParentPtr;

class Child : boost::noncopyable
{
 public:
  explicit Child(const ParentPtr& myMom_,
                 const ParentPtr& myDad_)
    : myMom(myMom_),
      myDad(myDad_)
  {
  }

 private:
  boost::weak_ptr<Parent> myMom;
  boost::weak_ptr<Parent> myDad;
};
typedef boost::shared_ptr<Child> ChildPtr;

class Parent : boost::noncopyable
{
 public:
  Parent()
  {
  }

  void setSpouser(const ParentPtr& spouser)
  {
    mySpouser = spouser;
  }

  void addChild(const ChildPtr& child)
  {
    myChildren.push_back(child);
  }

 private:
  boost::weak_ptr<Parent> mySpouser;
  std::vector<ChildPtr> myChildren;
};

int main()
{
  ParentPtr mom(new Parent);
  ParentPtr dad(new Parent);
  mom->setSpouser(dad);
  dad->setSpouser(mom);
  {
    ChildPtr child(new Child(mom, dad));
    mom->addChild(child);
    dad->addChild(child);
  }
  {
    ChildPtr child(new Child(mom, dad));
    mom->addChild(child);
    dad->addChild(child);
  }
}

如果不使用 smart pointer,用 C++ 做面向對象編程將會困難重重。

值語義與標準庫

C++ 要求凡是能放入標準容器的類型必須具有值語義。準確地說:type 必須是 SGIAssignable concept 的 model。但是,由 于C++ 編譯器會為 class 默認提供 copy constructor 和 assignment operator,因此除非明確禁止,否則 class 總是可以作為標準庫的元素類型——盡管程序可以編譯通過,但是隱藏了資源管理方面的 bug。

因此,在寫一個 class 的時候,先讓它繼承 boost::noncopyable,幾乎總是正確的。

在現代 C++ 中,一般不需要自己編寫 copy constructor 或 assignment operator,因為只要每個數據成員都具有值語義的話,編譯器自動生成的 member-wise copying&assigning 就能正常工作;如果以 smart ptr 為成員來持有其他對象,那么就能自動啟用或禁用 copying&assigning。例外:編寫 HashMap 這類底層庫時還是需要自己實現 copy control。

值語義與C++語言

C++ 的 class 本質上是值語義的,這才會出現 object slicing 這種語言獨有的問題,也才會需要程序員注意 pass-by-value 和 pass-by-const-reference 的取舍。在其他面向對象編程語言中,這都不需要費腦筋。

值語義是C++語言的三大約束之一,C++ 的設計初衷是讓用戶定義的類型(class)能像內置類型(int)一樣工作,具有同等的地位。為此C++做了以下設計(妥協):

  • class 的 layout 與 C struct 一樣,沒有額外的開銷。定義一個“只包含一個 int 成員的 class ”的對象開銷和定義一個 int 一樣。
  • 甚至 class data member 都默認是 uninitialized,因為函數局部的 int 是 uninitialized。
  • class 可以在 stack 上創建,也可以在 heap 上創建。因為 int 可以是 stack variable。
  • class 的數組就是一個個 class 對象挨著,沒有額外的 indirection。因為 int 數組就是這樣。
  • 編譯器會為 class 默認生成 copy constructor 和 assignment operator。其他語言沒有 copy constructor 一說,也不允許重載 assignment operator。C++ 的對象默認是可以拷貝的,這是一個尷尬的特性。
  • 當 class type 傳入函數時,默認是 make a copy (除非參數聲明為 reference)。因為把 int 傳入函數時是 make a copy。
  • 當函數返回一個 class type 時,只能通過 make a copy(C++ 不得不定義 RVO 來解決性能問題)。因為函數返回 int 時是 make a copy。
  • 以 class type 為成員時,數據成員是嵌入的。例如 pair<complex<double>, size_t> 的 layout 就是 complex<double> 挨著 size_t。

這些設計帶來了性能上的好處,原因是 memory locality。比方說我們在 C++ 里定義 complex<double> class,array of complex<double>, vector<complex<double> >,它們的 layout 分別是:(re 和 im 分別是復數的實部和虛部。)

value1

而如果我們在 Java 里干同樣的事情,layout 大不一樣,memory locality 也差很多:

value2

Java 里邊每個 object 都有 header,至少有兩個 word 的開銷。對比 Java 和 C++,可見 C++ 的對象模型要緊湊得多。

待續

下一篇文章我會談與值語義緊密相關的數據抽象(data abstraction),解釋為什么它是與面向對象并列的一種編程范式,為什么支持面向對象的編程語言不一定支持數據抽象。C++在最初的時候是以 data abstraction 為賣點,不過隨著時間的流逝,現在似乎很多人只知 Object-Oriented,不知 data abstraction 了。C++ 的強大之處在于“抽象”不以性能損失為代價,下一篇文章我們將看到具體例子。

posted on 2011-08-16 21:13 陳碩 閱讀(2815) 評論(4)  編輯 收藏 引用

評論

# re: C++ 工程實踐(8):值語義 2011-08-17 08:51 nk_ysg

學習下,lz這么久才更新。  回復  更多評論   

# re: C++ 工程實踐(8):值語義 2011-08-17 12:17 江浸月

樓主大牛啊,坐等樓主更新新文章。  回復  更多評論   

# re: C++ 工程實踐(8):值語義[未登錄] 2011-08-24 12:11 Chipset

shared_ptr, weak_ptr是廢物,除了降低速度和吃掉更多內存,我沒有見到什么好處。C++的析構函數可以以很小的代價避免很多資源管理方面的問題,但是為了遷就太多不熟悉C++的人不得不納入標準。

就算為了尊重人權允許用垃圾收集,引用計數設計的垃圾收集跟保守的標記清理似乎沒有發現什么優勢,看看Boehm吧。

把C++當Java用可能是最傻的事情,可惜太多程序員正在這么干,專門喜歡垃圾用法。

OO在歐美的上世紀七十年代就被看成雞肋,可惜亞太地區尤其中國很多程序員拿過來當寶貝,加上腐朽的科技支持力度,導致了太多的悲哀,IT技術水平跟歐美距離越來越大,而且加速拉大...  回復  更多評論   

# re: C++ 工程實踐(8):值語義 2011-08-31 08:40 Neuron Teckid

看標題還以為陳老師會談新標準中的 left value 和 rvalue reference. 不過有些東西還是很相通的, 比如對象復制構造在 cpp 中被嚴重濫用了, 大部分對象不應當有 "復制" 這樣的操作, 更多的是 "轉移", 我認為 c++0x 的 rvalue reference 正是為了修正這個 cpp 一直以來的設計漏洞產生的. 包括后面標準庫中 vector 對成員的要求也從 assignable and copyable 變成了 (assignable and copyable) or movable.

至于內存管理, 這個比較虛, 如果真有非常離奇復雜的對象引用關系, 是不是考慮該修改設計本身了, 反正我個人經歷接觸的對象模型多以樹形為主, 很少有 parent / child 這樣互搞的 :D  回復  更多評論   


只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


<2025年11月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

導航

統計

常用鏈接

隨筆分類

隨筆檔案

相冊

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            猛男gaygay欧美视频| 久久精品视频在线播放| 欧美日本簧片| 欧美国产日本| 亚洲欧美日韩视频一区| 91久久一区二区| 小黄鸭精品aⅴ导航网站入口| 你懂的视频欧美| 亚洲免费成人av| 国产精品看片资源| 国产精品有限公司| 又紧又大又爽精品一区二区| 在线电影国产精品| 日韩视频在线观看免费| 亚洲性线免费观看视频成熟| 性色一区二区三区| 久久婷婷麻豆| 欧美激情二区三区| 亚洲天堂av电影| 久久精品日韩欧美| 欧美日韩在线一区二区三区| 国产亚洲aⅴaaaaaa毛片| 狠狠综合久久av一区二区老牛| 亚洲精品中文字幕在线观看| 亚洲线精品一区二区三区八戒| 久久久亚洲欧洲日产国码αv| 久久久久久夜| 亚洲国产精品久久人人爱蜜臀| 久久综合九色99| 亚洲国产精彩中文乱码av在线播放| 91久久极品少妇xxxxⅹ软件| 亚洲性夜色噜噜噜7777| 男人的天堂亚洲| 国产精品日韩二区| 亚洲日本乱码在线观看| 久久精品九九| 一区二区三区高清在线| 另类尿喷潮videofree| 国产精自产拍久久久久久| 亚洲乱码国产乱码精品精98午夜| 久久综合给合久久狠狠色| 亚洲视频欧美在线| 欧美日本一区二区三区| 91久久久久久国产精品| 久久精品毛片| 亚洲一区一卡| 欧美视频四区| 日韩一级不卡| 欧美激情一区在线观看| 亚洲一区观看| 国产精品视频不卡| 亚洲一区影音先锋| 夜夜爽夜夜爽精品视频| 免费观看成人网| 亚洲高清资源综合久久精品| 久久嫩草精品久久久精品| 香港久久久电影| 国产免费亚洲高清| 亚洲在线中文字幕| 国产精品99久久久久久白浆小说| 欧美黑人国产人伦爽爽爽| 亚洲国产另类久久精品| 久久亚洲欧美| 欧美大片一区| 久久综合99re88久久爱| 午夜宅男欧美| 国产主播喷水一区二区| 久久精品国产亚洲一区二区三区 | 亚洲在线观看视频网站| 亚洲人被黑人高潮完整版| 免费短视频成人日韩| 亚洲福利电影| 91久久中文| 欧美视频不卡中文| 亚洲综合视频1区| 亚洲视频欧美视频| 国产一区再线| 欧美激情一区二区在线| 欧美日本不卡高清| 午夜国产精品视频| 欧美一区二区三区视频免费| 国内精品国产成人| 免费欧美高清视频| 欧美片在线观看| 午夜性色一区二区三区免费视频 | 91久久国产综合久久91精品网站| 欧美日韩国产在线| 亚洲综合色视频| 欧美中文在线观看国产| 悠悠资源网亚洲青| 亚洲欧洲在线播放| 国产欧美一区二区三区在线老狼| 亚洲欧美中日韩| 久久久国产精品一区二区三区| 在线免费不卡视频| 亚洲人永久免费| 国产伦理一区| 亚洲国产乱码最新视频| 国产精品一区二区欧美| 欧美激情视频在线播放| 欧美精品18+| 久久永久免费| 亚洲国产精品v| 国产精品亚洲综合久久| 亚洲成色777777在线观看影院| 国产精品久久久久999| 欧美激情一区二区三级高清视频| 国产欧美三级| 夜夜精品视频| 亚洲高清二区| 亚洲欧美成人一区二区三区| 亚洲精选一区二区| 久久久精品久久久久| 亚洲一区国产视频| 欧美成人精品一区二区| 久久综合久久综合九色| 欧美色视频日本高清在线观看| 牛牛精品成人免费视频| 国产精品美女xx| 亚洲欧洲在线视频| 在线观看亚洲视频啊啊啊啊| 美女亚洲精品| 亚洲精品在线一区二区| 欧美在线亚洲综合一区| 亚洲在线观看视频| 欧美激情91| 欧美激情四色 | 在线观看91精品国产麻豆| 亚洲少妇中出一区| 亚洲一级片在线看| 欧美激情小视频| 亚洲国内高清视频| 亚洲国产一区二区三区在线播 | 欧美激情视频一区二区三区不卡| 国内精品嫩模av私拍在线观看| 亚洲色无码播放| 亚洲免费一级电影| 国产精品久久久久久户外露出| 亚洲狼人综合| 一区二区三区免费观看| 欧美日韩视频不卡| 99国产精品久久久| 亚洲天堂av在线免费| 欧美日韩精品欧美日韩精品一| 亚洲欧洲美洲综合色网| 日韩天天综合| 欧美日韩另类字幕中文| 9i看片成人免费高清| 亚洲综合二区| 国产综合在线视频| 久久综合国产精品台湾中文娱乐网| 久久精品卡一| 在线观看国产日韩| 欧美aⅴ99久久黑人专区| 亚洲欧洲日本国产| 亚洲欧美日韩综合国产aⅴ| 国产嫩草影院久久久久 | 久久精品亚洲精品| 欧美成人亚洲成人日韩成人| 亚洲欧洲一级| 国产精品二区在线| 久久精品在这里| 亚洲精品色图| 欧美伊人久久久久久久久影院| 加勒比av一区二区| 欧美日本二区| 久久国产日韩欧美| 亚洲精一区二区三区| 亚洲欧美不卡| 精品成人在线观看| 欧美日韩亚洲一区二区三区| 亚洲视频播放| 欧美国产精品v| 亚洲欧美在线高清| 亚洲国产精品va在线观看黑人 | 欧美激情乱人伦| 亚洲一二三区在线| 欧美成人精品| 亚洲影院免费观看| 精品成人乱色一区二区| 国产精品夫妻自拍| 免费在线欧美黄色| 欧美中文在线观看| 一区二区日韩| 亚洲高清在线| 国产色综合天天综合网| 欧美国产日韩a欧美在线观看| 中文国产成人精品| 亚洲成人自拍视频| 国产日产亚洲精品系列| 欧美日韩国产高清| 久久亚洲精品欧美| 午夜视黄欧洲亚洲| 在线一区二区三区四区| 亚洲第一区色| 浪潮色综合久久天堂| 久久激情久久| 亚洲欧美国产制服动漫| 99视频一区二区| 亚洲国产精品久久精品怡红院|