我有一個問題,可能有一些我不知道的標準解決方案或設計模式。假設我有一個Rectangle這樣的類:
class Rectangle {
public:
void setShape(float top, float left, float width, float height);
void setCenter(float cx, float cy);
private:
float top, left, width, height, center_x, center_y;
}
現在,當一個用戶Rectangle呼叫setShape,該方法還設定了中心,如果用戶呼叫setCenter,該方法將修改top并left因此使得矩形的中心是它的左上角是一致的。
但是,在更復雜的設定中,很容易通過在 setter 中設定相關欄位而不修改其余欄位來引入錯誤,從而使整個物件保持一致并有意義。
是否有通用的解決方案/設計模式以某種方式強制執行,無論是在編譯時還是在運行時,物件將在每個 setter 完成后滿足一些不變性?
謝謝
uj5u.com熱心網友回復:
正如評論中指出的那樣,洗掉多余的成員肯定有助于防止不變數違規問題,但仍然有隱式不變數值得在您發布的代碼中檢查:例如width,height是正數。
無論如何,如何系統地處理不變檢查是很有趣的。理想情況下,我們應該具備以下所有條件:
- 從發布版本中去除不變檢查的編譯時開關
- 一種約定,例如
bool test_invariant() const成員方法。任何實作這一點的型別都被理解為具有可檢查的不變數。 - 默認為該成員的自由浮動函式,以便可以選擇加入任意型別,類似于
std::begin() - 例如
check_invariant(const T&),如果傳遞的物件具有失敗的不變數,則停止程式的函式。 - 能夠在
check_invariant(something)任何有意義的地方呼叫(例如在單元測驗中) check_invariant()在范圍退出時呼叫的 RAII 包裝器,以便正確處理例外和早期回傳。
聽起來很多,但其中大部分都可以隱藏在標題中,在適當的代碼中留下相當干凈的 API。
在一些 C 20 特性的幫助下,我們可以這樣做:
// invariant.h
#include <concepts>
#include <exception>
#include <iostream>
#include <source_location>
#include <string_view>
#ifdef _MSC_VER
#include <intrin.h> // for __debugbreak()
#endif
// Default to NDEBUG-driven if not explicitely set.
#ifndef MY_PROJECT_CHECK_INVARIANTS
#ifdef NDEBUG
#define MY_PROJECT_CHECK_INVARIANTS false
#else
#define MY_PROJECT_CHECK_INVARIANTS true
#endif
#endif
// Compile-time switch to enable/disable invariant checks
constexpr bool enable_invariant_checks = MY_PROJECT_CHECK_INVARIANTS;
// optional: Concepts, to get cleaner errors
template<typename T>
concept HasInvariantMethod = requires(const T& x) {
{x.test_invariant()} -> std::convertible_to<bool>;
};
template<typename T>
concept HasInvariant = requires(const T& x) {
{test_invariant(x)} -> std::convertible_to<bool>;
};
// Should be overloaded for types we can't add a method to.
template<HasInvariantMethod T>
[[nodiscard]] constexpr bool test_invariant(const T& obj) {
return obj.test_invariant();
}
// Performs invariant check if they are enabled, becomes a no-op otherwise.
template<HasInvariant T>
constexpr void check_invariant(
const T& obj,
std::string_view msg = {},
std::source_location loc = std::source_location::current()) {
if constexpr(enable_invariant_checks) {
if(!test_invariant(obj)) {
std::cerr << "broken invariant: "
<< loc.file_name() << "("
<< loc.line() << ":"
<< loc.column() << ") `"
<< loc.function_name() << "`: "
<< msg << '\n';
// break into the ddebugger if available
#ifdef _MSC_VER
__debugbreak();
#else
// etc...
#endif
// Invariant failures are inherently unrecoverable.
std::terminate();
}
}
}
// RAII-driven invariant checks.
// This ensures early returns and thrown exceptions are handled.
template<typename T>
struct [[nodiscard]] scoped_invariant_check {
constexpr scoped_invariant_check(const T& obj, std::source_location loc = std::source_location::current()) : obj_(obj), loc_(std::move(loc)) {
// Checking invariants upon entering a scope is technically
// redundant, but there's no harm in doing so.
check_invariant(obj_, "entering scope", loc_);
}
constexpr ~scoped_invariant_check() {
check_invariant(obj_, "exiting scope", std::move(loc_));
}
const T& obj_;
std::source_location loc_;
};
用法示例:
#include "invariant.h"
class Rectangle {
public:
void setShape(float top, float left, float width, float height) {
scoped_invariant_check check(*this);
// ...
top_ = top;
left_ = left;
// BUG! These could go negative
width_ = width;
height_ = height;
}
void setCenter(float cx, float cy) {
scoped_invariant_check check(*this);
// ...
}
bool test_invariant() const {
return
width_ >= 0.0f &&
height_ >= 0.0f;
}
private:
float top_ = 0.0f, left_ = 0.0f , width_ = 0.0f, height_ = 0.0f;
};
int main() {
Rectangle r;
r.setShape(1,1, -1, 12); // Bam!
}
在Godbolt上直播。
或者,如果你想保持簡單,你可以用 good-old 來近似大部分assert():
#include <cassert>
class Rectangle {
public:
void setShape(float top, float left, float width, float height) {
// ...
assert(test_invariant());
}
void setCenter(float cx, float cy) {
// ...
assert(test_invariant());
}
private:
bool test_invariant() const {
return ...;
}
float top, left, width, height;
};
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/356501.html
