天天看點

《More Effective C++》程式代碼詳解 第四階段

作者:明政面朝大海春暖花開

C++中的引用計數是一種記憶體管理技術,用于跟蹤對象的引用數量,并在引用計數減少到零時自動釋放對象的記憶體。下面是一個簡單的示例,示範了如何實作引用計數。

#include <iostream>

class RefCounted {
private:
    int count; // 引用計數
public:
    RefCounted() : count(0) {
        std::cout << "對象被建立" << std::endl;
    }

    void addRef() {
        count++;
    }

    void release() {
        count--;
        if (count == 0) {
            delete this;
            std::cout << "對象被銷毀" << std::endl;
        }
    }
};

int main() {
    RefCounted* obj = new RefCounted();
    obj->addRef(); // 增加引用計數
    obj->addRef();
    obj->release(); // 減少引用計數
    obj->release(); // 引用計數為0,對象被銷毀

    return 0;
}
           

在上面的示例中,RefCounted類具有一個私有成員變量count,用于存儲對象的引用計數。當對象被建立時,引用計數初始化為0。每當有新的引用指向對象時,調用addRef()方法增加引用計數。當引用不再需要時,調用release()方法減少引用計數。如果引用計數減少到零,表示沒有任何引用指向對象,此時可以安全地釋放對象的記憶體。

請注意,上述示例隻是一個簡單的引用計數實作,并沒有處理引用計數的線程安全性等問題。在實際應用中,可能需要考慮更多的細節和複雜性。

C++中的引用計數(Reference counting)是一種記憶體管理技術,用于跟蹤對象的引用數量。每當有一個新的引用指向對象時,引用計數就會增加;當引用不再指向對象時,引用計數就會減少。當引用計數為零時,表示沒有任何引用指向對象,對象可以被釋放。

以下是一個簡單的示例,示範了如何使用引用計數來管理對象的生命周期:

#include <iostream>

class ReferenceCountedObject {
public:
    ReferenceCountedObject() : refCount(0) {
        std::cout << "Object created." << std::endl;
    }

    ~ReferenceCountedObject() {
        std::cout << "Object destroyed." << std::endl;
    }

    void AddRef() {
        refCount++;
    }

    void Release() {
        refCount--;
        if (refCount == 0) {
            delete this;
        }
    }

private:
    int refCount;
};

int main() {
    ReferenceCountedObject* obj = new ReferenceCountedObject();
    obj->AddRef(); // 增加引用計數
    obj->Release(); // 減少引用計數

    return 0;
}
           

在上面的示例中,我們建立了一個名為ReferenceCountedObject的類,它包含了一個引用計數refCount。當建立一個新的對象時,引用計數為0。通過調用AddRef()方法,我們可以增加引用計數,表示有一個新的引用指向對象。當不再需要對象時,通過調用Release()方法來減少引用計數。當引用計數為零時,對象會被銷毀。

請注意,上述示例隻是一個簡單的示範,實際的引用計數實作可能會更加複雜,需要考慮線程安全性和其他細節。在實際開發中,可以使用智能指針等工具來簡化引用計數的管理。

C++程式設計語言本身并沒有要求或禁止對象在堆(heap)中建立。對象可以在堆上或棧上建立,具體取決于程式員的選擇和需求。

在C++中,對象在堆上建立時需要使用動态記憶體配置設定,通常通過使用new關鍵字來實作。下面是一個示例:

class MyClass {
public:
  MyClass() {
    // 構造函數
  }

  ~MyClass() {
    // 析構函數
  }
};

int main() {
  // 在堆上建立對象
  MyClass* obj = new MyClass();

  // 使用對象

  // 釋放對象的記憶體
  delete obj;

  return 0;
}
           

在上述示例中,new運算符用于在堆上建立MyClass類的對象,而delete運算符用于釋放對象的記憶體。

需要注意的是,堆上建立的對象在不再使用時需要手動釋放記憶體,否則會導緻記憶體洩漏。另外,使用堆上的動态記憶體配置設定需要謹慎,避免出現記憶體洩漏或懸空指針等問題。

相比之下,對象在棧上建立時不需要手動管理記憶體,它們的生命周期與其所在的作用域相對應。下面是一個在棧上建立對象的示例:

class MyClass {
public:
  MyClass() {
    // 構造函數
  }

  ~MyClass() {
    // 析構函數
  }
};

int main() {
  // 在棧上建立對象
  MyClass obj;

  // 使用對象

  return 0;
}
           

在上述示例中,MyClass類的對象obj在main函數的作用域内建立,當作用域結束時,對象會自動銷毀。

總而言之,C++允許對象在堆和棧上建立,具體取決于程式員的需求和設計。

在C++中,異正常格(exception specifications)是一種用于指定函數可能抛出的異常類型的機制。它們可以幫助程式員在函數聲明中明确指定可能抛出的異常,以便在函數調用時提供更好的文檔和錯誤處理。

然而,自從C++11起,異正常格已經不再推薦使用,因為它們在實踐中很難正确使用,并且對于編譯器的優化和代碼生成也有負面影響。相反,現代C++更傾向于使用異常安全(exception safety)和異常處理(exception handling)技術來管理和處理異常。

盡管如此,我仍然可以給你舉一個使用異正常格的例子:

#include <iostream>

void foo() throw(int, double) {
    int x = 10;
    if (x > 5) {
        throw 42; // 可能抛出int類型異常
    } else {
        throw 3.14; // 可能抛出double類型異常
    }
}

int main() {
    try {
        foo();
    } catch (int ex) {
        std::cout << "捕獲到int類型異常:" << ex << std::endl;
    } catch (double ex) {
        std::cout << "捕獲到double類型異常:" << ex << std::endl;
    }
    
    return 0;
}
           

在上面的示例中,函數foo()使用了異正常格throw(int, double)來指定它可能抛出的異常類型。在函數内部,根據條件,它可能抛出一個整數類型的異常(42)或一個雙精度浮點數類型的異常(3.14)。在main()函數中,我們使用try-catch語句來捕獲并處理這些異常。

請注意,盡管這個例子展示了異正常格的使用,但這種用法已經過時,不再被推薦。更好的做法是使用異常安全和異常處理技術來管理和處理異常。

C++的"謹記80-20法則"是指在程式設計中,80%的時間應該用于解決問題的核心部分,而隻有20%的時間應該用于解決次要的細節問題。這個法則的目的是讓程式員将更多的精力放在解決核心問題上,而不是被瑣碎的細節問題所困擾。

以下是一個簡單的例子來說明這個法則:

假設你正在編寫一個程式來計算一個數組中所有元素的平均值。根據80-20法則,你應該将大部分時間花在編寫計算平均值的核心算法上,而不是被輸入輸出、錯誤處理等細節問題所分散注意力。

#include <iostream>
#include <vector>

double computeAverage(const std::vector<double>& numbers) {
    double sum = 0.0;
    for (const auto& num : numbers) {
        sum += num;
    }
    return sum / numbers.size();
}

int main() {
    std::vector<double> nums = {1.0, 2.0, 3.0, 4.0, 5.0};
    double average = computeAverage(nums);
    std::cout << "Average: " << average << std::endl;
    
    return 0;
}
           

在這個例子中,主要的核心算法是計算數組中所有元素的總和并除以元素個數。這裡我們将大部分時間和精力放在了實作這個核心算法上,而細節問題如輸入輸出和錯誤處理則被簡化了。這樣做可以讓我們更專注于解決問題的核心部分。

總之,謹記80-20法則可以幫助程式員更有效地配置設定時間和精力,将更多的注意力放在解決問題的核心部分上,提高程式設計效率。

C++ 中的 lazy evaluation(緩式評估)是一種延遲計算的政策,它允許在需要時才計算表達式的值。這種政策可以提高程式的性能和效率,尤其是在處理大量資料或複雜計算時。

下面是一個簡單的例子,示範了如何在 C++ 中使用 lazy evaluation:

#include <iostream>
#include <functional>

// 定義一個 lazy evaluation 的類
template <typename T>
class Lazy {
private:
    std::function<T()> expression;
    T value;
    bool evaluated;

public:
    Lazy(std::function<T()> expr) : expression(expr), evaluated(false) {}

    T getValue() {
        if (!evaluated) {
            value = expression();
            evaluated = true;
        }
        return value;
    }
};

int main() {
    int x = 5;
    int y = 10;

    // 定義一個 lazy evaluation 的表達式
    Lazy<int> lazySum([&]() {
        std::cout << "Calculating sum..." << std::endl;
        return x + y;
    });

    // 在需要時才計算表達式的值
    std::cout << lazySum.getValue() << std::endl;  // 輸出:Calculating sum... 15
    std::cout << lazySum.getValue() << std::endl;  // 輸出:15(不再計算,直接使用之前計算的值)

    return 0;
}
           

在上面的例子中,我們定義了一個 Lazy 類,它接受一個傳回類型為 T 的函數對象作為參數。在 getValue() 方法中,我們檢查是否已經對表達式進行了計算,如果沒有則進行計算,并将結果儲存在 value 成員變量中,然後傳回該值。如果已經進行了計算,則直接傳回之前計算的結果。

在 main() 函數中,我們建立了一個 Lazy<int> 類型的對象 lazySum,并傳入一個 lambda 表達式作為參數。這個 lambda 表達式計算了兩個整數的和,并輸出一條資訊。當我們第一次調用 lazySum.getValue() 時,會執行 lambda 表達式中的代碼進行計算,并輸出計算資訊和結果。當我們再次調用 lazySum.getValue() 時,由于已經進行了計算,将直接傳回之前計算的結果,而不再執行 lambda 表達式中的代碼。

這就是一個簡單的使用 lazy evaluation 的示例,你可以根據具體的需求和場景來靈活應用它。

`lazySum` 是一個 `Lazy<int>` 類型的對象,它使用了一個 lambda 表達式作為參數進行初始化。lambda 表達式 `[&]() { /* code */ }` 是一個匿名函數,其中的 `&` 符号表示以引用方式捕獲目前作用域中的所有變量。

在這種情況下,lambda 表達式使用了預設的捕獲方式 `[&]`,表示以引用方式捕獲目前作用域中的所有變量。這意味着 lambda 表達式可以通路并修改外部作用域中的變量。

`lazySum` 是一個惰性求值對象,它延遲計算結果直到真正需要使用結果的時候。在這個例子中,lambda 表達式定義了計算整數和的邏輯,但是并不立即執行。隻有在調用 `lazySum` 對象的成員函數時,才會執行 lambda 表達式并傳回計算結果。

簡而言之,`lazySum` 是一個惰性求值對象,使用 lambda 表達式作為參數進行初始化,可以在需要的時候延遲計算整數和。

在C++中,臨時對象是指在表達式求值期間建立的臨時對象,它們通常用于存儲中間結果或作為函數的傳回值。臨時對象的來源可以有多種情況,下面是一些常見的例子:

  1. 函數傳回臨時對象:當函數傳回一個對象時,編譯器會建立一個臨時對象來存儲傳回值,例如:
class MyClass {
public:
    MyClass(int value) : m_value(value) {}
private:
    int m_value;
};

MyClass createObject() {
    return MyClass(10);
}

int main() {
    MyClass obj = createObject();
    // 這裡的createObject()函數傳回一個臨時對象,然後将其指派給obj
    return 0;
}
           
  1. 對象的複制構造函數:當使用一個對象初始化另一個對象時,編譯器會調用複制構造函數來建立一個臨時對象,例如:
class MyClass {
public:
    MyClass(const MyClass& other) {
        // 複制構造函數的實作
    }
};

int main() {
    MyClass obj1;
    MyClass obj2 = obj1;
    // 這裡将obj1指派給obj2時,會調用複制構造函數建立一個臨時對象
    return 0;
}
           
  1. 類型轉換:當進行隐式類型轉換時,編譯器會建立一個臨時對象來存儲轉換結果,例如:
class MyClass {
public:
    MyClass(int value) : m_value(value) {}
private:
    int m_value;
};

void printValue(MyClass obj) {
    // 列印obj的值
}

int main() {
    printValue(10);
    // 這裡将整數10隐式轉換為MyClass對象時,會建立一個臨時對象
    return 0;
}
           

這些隻是臨時對象的一些常見來源,實際上還有其他情況下會建立臨時對象。了解臨時對象的來源可以幫助我們更好地了解C++中的對象生命周期和表達式求值過程。

在C++中,臨時對象是指在表達式求值過程中臨時建立的對象,它們沒有被明确命名,通常用于執行某些臨時計算或傳遞給函數。

以下是一個使用臨時對象的示例:

#include <iostream>

class MyClass {
public:
  MyClass() {
    std::cout << "Constructor called!" << std::endl;
  }

  ~MyClass() {
    std::cout << "Destructor called!" << std::endl;
  }
};

void func(MyClass obj) {
  std::cout << "Inside func()" << std::endl;
}

int main() {
  func(MyClass()); // 建立一個臨時對象并傳遞給函數func

  return 0;
}
           

在上面的示例中,MyClass是一個簡單的類,它有一個構造函數和一個析構函數。在main函數中,我們調用了func函數,并将一個臨時建立的MyClass對象作為參數傳遞給它。這個臨時對象在函數調用結束後會被銷毀,是以在輸出中可以看到構造函數和析構函數的調用順序。

請注意,臨時對象的生命周期非常短暫,它們隻在需要時被建立,并在使用完畢後立即銷毀。臨時對象的建立和銷毀是由編譯器自動處理的,無需顯式地進行管理。

繼續閱讀