1. 程式人生 > >C++物件語義與值語義

C++物件語義與值語義

一、值語義

  • 所謂值語義是一個物件被系統標準的複製方式複製後,與被複制的物件之間毫無關係,可以彼此獨立改變互不影響

實現

C++

Java

標準複製方式

賦值、拷貝構造

賦值,clone()

基本資料型別

包括整數,浮點數,布林值,字元,指標等,全部為值語義

包括整數,浮點數,布林值,字元等,全部為值語義

自定義型別

不包含資源的自定義型別,系統提供的預設拷貝建構函式與賦值操作符亦保證了值語義;包含資源的自定義型別,需要提供深拷貝操作的拷貝建構函式和賦值操作符,並在建構函式中獲取資源,在解構函式中釋放資源

沒有十分自然的方式將Java物件實現為值語義,通常採用“不可變類”的方式,如String,或遞迴實現深度clone()

優點

  • 避免別名問題導致的意外修改

  • 避免對共享資源的引用引起的釋放時機和額外的引用計數問題

缺點

  • 佔用較多記憶體

應用

  • 通常將具有“等價性”的物件實現為值語義,如Money,你的10塊錢跟我的10塊錢沒什麼兩樣,完全可以換過來用

舉例

C++

Java

vector<int> first;

vector<int> second = first;

second.push_back(1000);

cout << first.size(); //0

cout << second.size();//1

String first = "abc";

String second = first;

first = first.replace('a''x');

System.out.println(first); //xbc

System.out.println(second);//abc 

二、物件語義

  • 也叫指標語義,引用語義等,通常是指一個物件被系統標準的複製方式複製後,與被複制的物件之間依然共享底層資源,對任何一個的改變都將改變另一個

實現

C++

Java

標準複製方式

賦值、拷貝構造

賦值,clone()

自定義型別

包含資源的自定義型別,沒有提供拷貝建構函式和賦值操作符,或者在拷貝建構函式和賦值操作符中有意共享資源,則此時的物件具有指標語義

預設全為指標語義,除非該類及其成員遞迴實現深度clone(),或實現為“不可變類”

優點

  • 佔用較少記憶體

缺點

  • 別名問題導致的意外修改,尤其應主意用於模組間介面的引數和返回值

  • 資源釋放的額外負擔,通常是引用計數

應用

  • 通常將必須保持“同一性”的物件實現為物件語義,如帳戶,雖然你的帳戶和我的帳戶此時都只剩100塊錢了,但你的是你的,我的是我的,將來你發了財也只會存到你帳戶上;具有“同一性”的物件通常在系統中有唯一ID,這類物件通常不可複製,因為複製沒有現實意義,如網路埠,資料庫連結

舉例

C++

Java

vector<int> obj(2, 0);

vector<int>::iterator first = obj.begin();

vector<int>::iterator second = first;

cout << *first; //0

cout << *second;//0

*first = 1;

cout << *first; //1

cout << *second;//1

List<Integer> first = newArrayList<Integer>();

List<Integer> second = first;

second.add(1000);

System.out.println(first.size()); //1

System.out.println(second.size());//1

注:

  1. 即使在具有垃圾回收的系統中,資源釋放依然是個問題,因為資源不止記憶體,還有網路埠,資料庫連結等

  2. 錄一篇 wangtianxing 老師的觀點,比我說的清楚有趣的多:

我認為“值”與“物件”的區分在設計和實現時具有非常重要的指導作用,
因此下面做一些說明。

“值”與“物件”型別之間並沒有嚴格定義的區分。但通常可以觀察到下列不同:

0. “值”是“死的”、“傻的”、“簡單的”、“具體的”、“可複製的”,
“物件”是“活的”、“聰明的”、“複雜的”、“抽象的”、“不可複製的”。

這裡的“複雜性”主要還是指行為的複雜性,而非結構的複雜性。例如,

list< map< vector<string>, deque< set<int> > > >

仍然是一個不折不扣的“值”型別。

這裡我們不在哲學的路上走太遠,還是看看下面更具體的一些特徵吧。

1. “值”的成員函式(包括解構函式)都不是 virtual 的,不是設計來被繼承的。
“物件”的解構函式是 virtual 的,而且通常還有其它的 virtual 成員函式,
是設計來被繼承的,或繼承了其他基類。

// value:
struct String {
// non-virtual destructor:
~String() { delete[] s_; }
private:
char* s_;
};

// string should not be public-ly inherited.

// object:
struct OutputDevice {
virtual ~OutputDevice();
virtual void output( char const* ) = 0;
};

struct ConsoleOutputDevice : OutputDevice {
// inherits virtual destructor,
// overrides virtual member function:
void output( char const* );
};

2. “物件”經常必須通過指標或引用來使用,“值”不一定需要。

::std::auto_ptr<OutputDevice> output( new ConsoleOutputDevice() );

void f( OutputDevice& dev )
{
// ...
dev.output( "blah blah/n" );
// ...
}

String s = "dsjflsdjflsjlf";
String t = "djslfdsjfsl";
s += t;

void g( String s ) // by-value is OK.
{
// ...
}

g( s );


3. “值”可以複製出任意多份等價物,是 Assignable, CopyConstructible 的
(C++標準裡定義了Assignable, CopyConstructible 的具體含義)。
“物件”通常不是 Assignable 也不是 CopyConstructible 的,通常是被共享的
不是被複制的。 即使“物件”被“克隆”,“克隆”出來的物件與原物件也不是
完全等價的:相同的基因,不同的個體。

string s = "dsjlfsdjlfsdjlfsdjl";
string t;
t = s;
// the following two lines are equivalent:
cout << s;
cout << t;

auto_ptr<OutputDevice> dev1( new ConsoleOutputDevice(...) );
auto_ptr<OutputDevice> dev2( dev1.clone() );
// the following two lines are NOT equivalent:
dev1->output( "blah blah" );
dev2->output( "blah blah" );

這裡,dev2 可能具有與 dev1 相同的位置、大小、許可權等屬性,但卻是
完全不同的另一個視窗。

4. 對於“值”型別,應該嚴格保證 constness-correctness,對於“物件”型別,
通常不需要,甚至有些 'const' 是有害的。

// bad design of a value type:
struct Contact {
string address(); // non-const
};

void f( Contact const& contact )
{
cout << contact.address(); // won't compile.
}

// maybe bad design of an intelligent object type:
struct Robot {
string name( Object const& asker ) const // const!
{ return my_name; }
private:
string my_name;
};

由於 Robot 的智慧,問他一次名字也可能要觸發它內部狀態的改變。
一般來說,我們不應該對一個複雜“物件”假設任何操作不改變其內部狀態,
因此其介面上不應有 const 成員函式。 使用一個 const Robot
也是毫無意義的。如果你覺得 Robot 比較特殊,可以想想 const Database。

5. “值”是可以比較的,“物件”通常是不可比較的,要比較的話,
應該比較物件的地址,而不是內容。

根據“值”型別表達的概念的特點,比較關係運算可分為兩個層次:

a 相等性比較:

bool operator == ( T const& ) const;
bool operator != ( T const& ) const
{ return ! operator == ( other ); }

而且 operator !=( other ) 必須等價於 ! operator == ( other ),
operator == () 必須是一個“等價關係”(equivalence relationship):
任給 T a, b, c:

(自反的) (a == a) => true.
(對稱的) (a == b) => (b == a).
(傳遞的) (a == b && b == c) => (a == c).

對於一個型別 T 的兩個物件 a, b,如果表示式 (a == b) 可以轉換為
bool 型別,而且這個 operator == () 是 T 上的一個等價關係,那麼
就說 T 是 EqualityComparable 的。

b 排序關係比較:

bool operator < ( T const& ) const;
bool operator > ( T const& other ) const
{ return other < *this; }
bool operator >= ( T const& other ) const
{ return ! operator < ( other ); }
bool operator <= ( T const& other ) const
{ return ! operator > ( other ); }

這些運算之間的關係必須滿足上面給出的實現所表達的等價性。
(因此通常只實現 operator<(),然後從這裡 copy 另外三個!)
operator <() 必須是 T 上的一個“嚴格弱序”
(strict weak ordering)關係:

任給 T a, b, c:

(irreflexive) (a < a) => false.
(transitive) (a < b && b < c) => (a < c).
(weak ordering)
定義 eqiv(a,b) = ! (a < b) && ! (b < a).
equiv(a,b) && equiv(b,c) => eqiv(a,c).

這樣,operator<() 可以在 equiv 決定的 T 的等價類上定義一個全序
(total ordering).

對於一個型別 T 的兩個物件 a, b,如果表示式 (a < b) 可以轉換為
bool 型別,而且這個 operator < () 是 T 上的一個嚴格弱序關係,
那麼就說 T 是 LessThanComparable 的。

一個“值”型別可以只實現相等性比較。如果同時實現了排序關係比較,
那麼必須有 (a == b) iff (! (a < b) && ! (b < a))。

上述對關係運算的要求來自於 C++ 標準中對容器類中物件的要求。即使
你的型別的物件不被放到標準容器中,也應該滿足上述要求,否則就可能
產生一些讓他人吃驚的行為。

綜上所述,在設計一個類時,如果思考一下這個類表達的是一個簡單的“值”
還是一個複雜的“物件”,非常有助於決定類的介面以及用法的許多方面:

. 會不會被繼承/有沒有 virtual 函式?
. 是否允許拷貝和賦值?還是必須共享/克隆?
. 如何對待 constness-correctness?
. 是否應該實現比較運算?實現哪些?

等等等等。

(除了“值”和“物件”的區分外,還有一批C++中的型別可以歸類為
表達了一個“概念”,但那是另一個話題)

"三月的外星人" <[email protected]> 寫入訊息
news:[email protected]
> 標準中一些具體的類是否都應當看作是“值”類,比如“map",list,vector等?
> 而一些抽象的類就可以看成是”物件“類?

標準的資料型別大致有以下幾類:

“值”:

各種基本型別:int, double, ...。各種指標。complex<>。
各種容器: vector<>, list<>, multi/set<>, multi/map<>, deque<>,
bitset<>, basic_string<>, valarray<>... 以及它們的各種 iterator。
adaptors: stack<>, queue<>, priority_queue<>。
各種 functor: less, greater, equal_to, plus, minus, binder1st...。
locale 是把“物件”型別通過共享方式包裝成“值”型別,以方便使用。


“物件”:

iostream相關類:basic_ios<>, basic_*stream<>, basic_*streambuf<>。
各種facet: ctype_base/ctype<>, codecvt_base/codecvt<>...。
type_info。


“值與物件的混合物”

異常類:exception,bad_exception, logic_error, range_error...。
可以象“值”一樣複製,但又有繼承層次,可以通過基類(exception)
引用或指標來使用。這種設計在應用程式裡最好避免,因為很容易引起
object-slicing:

exception e = domain_error( "sqrt(NegativeNumber)" );

上面一行能編譯,能執行,但沒有做想讓它做的事。


“概念實現”:(象“值”一樣簡單,但不是用來裝一個“值”的)

iterator<>, input_iterator_tag, output_iterator_tag...,
unary_function<>, binary_function<>...。


“特徵描述”

iterator_traits<>, char_traits<>, numeric_limits<>...。


“怪物”:

auto_ptr<>。
 




轉載於: http://blog.csdn.net/chelsea/article/details/439332

相關推薦

C++物件語義語義

一、值語義 所謂值語義是一個物件被系統標準的複製方式複製後,與被複制的物件之間毫無關係,可以彼此獨立改變互不影響 實現 C++ Java 標準複製方式 賦值、拷貝構造 賦值,clone() 基本資料型別 包括整數,浮點數,布林值,字元,指標等,全部為值語義 包括整數,浮點

C語言缺陷陷阱語義分析

語義分析 (程式設計師本意是希望表示某種事物,而實際表示的卻是另外一種事物) 1.     指標和陣列 陣列值必須注意的兩點:(1)C語言只有一維陣列,且陣列大小必須在編譯期作為一個常數確定下來;(2)對一個數組只能進行:確定該陣列的大小,以及獲得指向該陣列下標為0

C 物件 檔案二進位制串(byte陣列)之間的轉換

                     1.關於本文在使用C#下的TCP(類TcpClient)、UDP(類UdpClient)協議傳輸資訊時,都需要將資訊轉換為byte型別的陣列進行傳送。本文實現了兩種object與byte陣列的轉換和一種檔案與byte陣列轉換的方式。基礎型別的資料,可以用BitConv

C++物件屬性初始化規則

推薦使用初始化列表初始化 初始化列表我們不寫,但是編譯器依然會自動初始化一次 ———》針對自定義型別 宣告和定義,宣告是告訴程式我要定義這個東西 定義是實際開闢空間,分配記憶體 初始化列表可以認為初始化列表是成員變數定義的地方。 必須放在初始化列表:    

C++物件到bool的轉換

問題 最近在使用pugixml,在閱讀原始碼自帶的例程時(docs/samples/load_error_handling.cpp),發現有段程式碼比較迷惑, 這裡生成了一個xml_parse_result的物件result,用於接受doc.load_s

C++之語義物件語義

●iostream擴充套件 #include <ostream>  // 是不是太重量級了? class Date {  public:   Date(int year, int month, int day)     : year_(year), month_

C++11 標準新特性: 右引用轉移語義

https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/ 特性的目的 右值引用 (Rvalue Referene) 是 C++ 新標準 (C++11, 11 代表 2011 年 ) 中引入的新特性

C++11移動語義探討——從臨時物件到右引用

一.前言 這篇文章主要談談c++11中引入的右值引用概念和移動語義概念。以及這些東西可能在我們程式設計中帶來哪些體驗、便捷或者是程式碼效率的提高。 文章主要分為以下三點: 臨時物件的產生 何謂右值引用 何謂移動語義 二.臨時物件的產生 在我們以

c++ 特性: 右引用移動語義

c++11中引入了右值引用。 左值與右值 先區分左值與右值,這裡參考c++ 右值引用中對左值和右值區分方法: 左值和右值都是針對表示式,左值是指表示式結束後依然存在的持久物件,右值是指表示式結束時就不再存在的臨時物件。 一個區分左值和右值的簡便方法是

[C++] 右引用:移動語義完美轉發

C++11 引入的新特性中,除了併發記憶體模型和相關設施,這些高帥富之外,最引人入勝且接地氣的特性就要屬『右值引用』了(rvalue reference)。加入右值引用的動機在於效率:減少不必要的資源拷貝。考慮下面的程式: std::vector<string>

C++11執行緒指南(4)--右引用移動語義

1. 按值傳遞   什麼是按值傳遞?   當一個函式通過值的方式獲取它的引數時,就會包含一個拷貝的動作。編譯器知道如何去進行拷貝。如果引數是自定義型別,則我們還需要提供拷貝建構函式,或者賦值運算子來進行深拷貝。然而,拷貝是需要代價的。在我們使用STL容器時,就存在大量的拷貝

C++11中的`移動語義``右引用`的介紹討論

本文主要介紹了C++11中的移動語義與右值引用, 並且對其中的一些坑做了深入的討論. 在正式介紹這部分內容之前, 我們先介紹一下rule of three/five原則, 與copy-and-swap idiom最佳實踐. 本文參考了stackoverflow上的一些回答. 不能算是完全原創 rule

[技術]淺談初始化語義語義

真的 class 基本 復制構造函數 spa 數值 復制構造 得到 對數 背景 博主是一個常年使用初始化語義的coder= =,所以經常會遇到這樣的對話 int tmp(0); XXX:誒,你這tmp函數是幹什麽的啊 博主:蛤?我哪裏定義了tmp函數了

深度探索C++物件模型——物件(5)——程式轉化語義

我們寫的程式碼,編譯器會對程式碼進行拆分,拆分成編譯器更容易理解和實現的程式碼,接下來將從我們程式設計師寫程式碼角度和編譯器理解角度去分析一些情況 1.定義時初始化物件 (1)程式設計師角度 #include <iostream> using namespace std;

深度探索C++物件模型(4)——物件(4)——拷貝建構函式語義

    傳統認識認為:如果我們沒有定義一個自己的拷貝建構函式,編譯器會幫助我們合成 一個拷貝建構函式。     結論:這個合成的拷貝建構函式,也是在 必要的時候才會被編譯器合成出來。 示例程式碼 class A { public

深度探索C++物件模型(3)——物件(3)——建構函式語義

預設建構函式(預設建構函式)分析      即沒有引數的建構函式      傳統認識認為:如果我們自己沒定義任何建構函式,那麼編譯器就會為我們隱式自動定義 一個預設的建構函式, 我們稱這種建構函式為:“合成的預設建構函式” &n

C++第三章(類和物件)下篇 (動態建立釋放,物件的賦和複製,靜態資料成員,友元)

一,物件的動態建立與釋放 new (程式設計師怎麼會沒有朋友??? 來讓我給你new 一個) 在我學過的c中 我們一般都用 malloc 來動態申請空間。現在我們可以用new 來申請空間 Box *pt; pt = new Box; c++允許在new時進行賦值 Box *

引用move語義

右值 C語言中,左值(left value, lvalue)只出現在賦值符左邊的量,右值(right value, rvalue)是出現在賦值符右邊的量。在C++中,右值的定義稍微不同,每一個表示式都會產生一個左值或者右值,所以表示式也稱左值表示式或右值表示式

C++物件C#物件----C++C#傳遞引用傳遞淺析

在C#中,看下面一段程式: class A     {         public int value;         public A(int x)         {             value = x;         }         public

C++11特性--右引用,移動語義,強制移動move()

1.右值引用   *右值:不能對其應用地址運算子的值。   *將右值關聯到右值引用導致該右值被儲存到特定的位置,且可以獲取該位置的地址   *右值包括字面常量(C風格字串除外,它表示地址),諸如X+Y等表示式以及返回值得函式(條件是該函式返回的不是引用)   *引入右值引用的主要目的之一是實行移動語義   E