the c++ core guidelines




CppCoreGuidelines C.21是否正確? (2)

我對這個指導方針有很大的保留意見。 即使知道這是一個指引 ,而不是一個規則 ,我仍然有所保留。

假設您有一個類似於std::complex<double>std::chrono::seconds的用戶編寫的類。 這只是一種價值類型。 它不擁有任何資源,它的意思是簡單的。 假設它有一個非特殊成員構造函數。

class SimpleValue
{
    int value_;
public:
    explicit SimpleValue(int value);
};

那麼,我也希望SimpleValue是默認的可構造的,我已經通過提供另一個構造函數來禁止默認構造函數,所以我需要添加特殊成員

class SimpleValue
{
    int value_;
public:
    SimpleValue();
    explicit SimpleValue(int value);
};

我擔心人們會記住這個方針和理由:那麼,因為我提供了一個特殊的成員,我應該定義或刪除其餘的,所以這裡...

class SimpleValue
{
    int value_;
public:
    ~SimpleValue() = default;
    SimpleValue();
    SimpleValue(const SimpleValue&) = default;
    SimpleValue& operator=(const SimpleValue&) = default;

    explicit SimpleValue(int value);
};

嗯...我不需要移動成員,但我需要盲目地遵循聰明人告訴我的,所以我只是刪除這些:

class SimpleValue
{
    int value_;
public:
    ~SimpleValue() = default;
    SimpleValue();
    SimpleValue(const SimpleValue&) = default;
    SimpleValue& operator=(const SimpleValue&) = default;
    SimpleValue(SimpleValue&&) = delete;
    SimpleValue& operator=(SimpleValue&&) = delete;

    explicit SimpleValue(int value);
};

我擔心CoreCppGuidelines C.21會導致大量的代碼看起來就像這樣。 為什麼那麼糟糕? 幾個原因:

1.這比正確的版本更難以閱讀:

class SimpleValue
{
    int value_;
public:
    SimpleValue();
    explicit SimpleValue(int value);
};

它被打破了 。 你會發現你第一次嘗試從值函數返回一個SimpleValue值:

SimpleValue
make_SimpleValue(int i)
{
    // do some computations with i
    SimpleValue x{i};
    // do some more computations
    return x;
}

這不會編譯。 該錯誤消息將說明訪問SimpleValue的已刪除成員。

我有一些更好的指導方針:

1.知道編譯器何時默認或刪除特殊成員,以及默認成員會做什麼。

這個圖表可以幫助:

如果這個圖表太複雜,我明白。 這複雜。 但是,一次向你解釋一下,處理起來要容易得多。 希望能夠在一個星期內更新這個答案,並附上我的一個視頻鏈接,解釋這個圖表。 這是解釋的鏈接,經過比我想要的更長的延遲(我的道歉): https//www.youtube.com/watch?v = vLinb2fgkHk

2.編譯器的隱式操作不正確時,總是定義或刪除一個特殊成員。

3.不要依賴已棄用的行為(上圖中的紅色框)。 如果聲明任何析構函數,複製構造函數或複制賦值運算符,則聲明復制構造函數和復制賦值運算符。

4. 永遠不要刪除移動成員。 如果你這樣做,充其量也是多餘的。 最糟糕的是它會打破你的課堂(就像上面的SimpleValue例子)。 如果你刪除移動成員,這是多餘的情況下,那麼你強迫你的讀者不斷審查你的課程,以確保它不是破案。

5.即使結果是讓編譯器為你處理它(可能通過隱含地禁止或刪除它們),給予6個特殊成員中的每一個特別的成員以溫柔的關懷。

6.把你的特殊成員按照一致的順序排列在你的課程頂部(只有你想要明確聲明的成員),這樣你的讀者才不必去搜索它們。 我有我最喜歡的訂單,如果你喜歡的訂單是不同的,很好。 我的首選順序是我在SimpleValue示例中使用的SimpleValue

在閱讀Bjarne Stroustrup的CoreCppGuidelines時,我發現了一個與我的經驗相矛盾的指導方針。

C.21要求如下:

如果您定義或=delete任何默認操作,請定義或=delete全部

有以下原因:

特殊函數的語義是密切相關的,所以如果需要非默認的話,其他人也需要修改。

根據我的經驗,重新定義違約操作的兩個最常見的情況如下:

#1:具有默認主體的虛擬析構函數的定義以允許繼承:

class C1
{
...
    virtual ~C1() = default;
}

#2:對RAII類型成員進行一些初始化的默認構造函數的定義:

class C2
{
public:
    int a; float b; std::string c; std::unique_ptr<int> x;

    C2() : a(0), b(1), c("2"), x(std::make_unique<int>(5))
    {}
}

所有其他情況在我的經驗中是罕見的。

你怎麼看待這些例子? 它們是C.21規則的例外還是最好在這裡定義所有的默認操作? 還有其他頻繁的例外情況嗎?