פתרון זהות ובעלות ברכיב¶
פתרון התרגיל הראשון¶
אחרי create_value המונה הוא אחד והבעלים הוא reader. הבירור של writer מעלה לשניים, ושני בירורי הזהות מעלים לארבעה. השחרורים של second וfirst מורידים לשניים, ואז שני הממשקים המקוריים משוחררים. האובייקט נמחק בשחרור האחרון, בלי קשר לשאלה איזו כתובת ממשק שוחררה אחרונה.
בקשה לממשק חסר צריכה להחזיר E_NOINTERFACE ולהשאיר output ריק. אפשר להוסיף לmain את הבדיקה הבאה אחרי יצירת reader. זהו קטע שמצטרף לתוכנית מההרצאה.
const IID absent = {0x8be61834,0x27a1,0x4e49,{0x98,0xab,1,2,3,4,5,6}};
void* missing = reader; // Deliberately non-null before the call.
const HRESULT absent_hr = reader->QueryInterface(absent, &missing);
std::printf("missing=%d cleared=%d\n",
absent_hr == E_NOINTERFACE, missing == nullptr);
פתרון התרגיל השני¶
הממשק החדש מקבל IID חדש ומתווסף לירושה ולQueryInterface של האובייקט. המימוש הבא הוא גוף המתודה בתוך Value, ולכן יש לו גישה לvalue_. את ההצהרה באותו סדר פרמטרים מוסיפים לממשק החדש אחרי שלוש מתודות הבסיס.
HRESULT STDMETHODCALLTYPE CompareReplace(LONG expected, LONG desired,
BOOL* replaced) {
if (!replaced) return E_POINTER;
*replaced = value_.compare_exchange_strong(
expected, desired, std::memory_order_relaxed) ? TRUE : FALSE;
return S_OK;
}
הצלחת HRESULT אומרת שבקשת ההשוואה בוצעה; ערך replaced אומר האם התנאי התקיים. אם שני threadים קיבלו references עצמאיים לפני ההתחלה ושניהם מנסים להחליף 0 בערכים שונים שאינם 0, בדיוק אחד מהם יצליח כל עוד אין כותב נוסף. הבדיקה היא אטומית על המספר הזה; היא לא מפרסמת אוטומטית מבני נתונים צדדיים. במקרה כזה נצטרך להגדיר גם ordering מתאים ואת חיי אותם נתונים.
פתרון התרגיל השלישי¶
אפשר להוסיף את הclass הבא לאותו קובץ. המפעל מחזיק מונה משלו; כל אובייקט שהוא יוצר מחזיק מונה נפרד. ההחזרה של E_NOTIMPL בLockServer מכוונת למעבדה המקומית בלבד.
class LocalFactory final : public IClassFactory {
std::atomic<ULONG> refs_{1};
public:
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** out) override {
if (!out) return E_POINTER;
*out = nullptr;
if (!IsEqualIID(iid, IID_IUnknown) && !IsEqualIID(iid, IID_IClassFactory))
return E_NOINTERFACE;
*out = static_cast<IClassFactory*>(this);
AddRef();
return S_OK;
}
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
ULONG STDMETHODCALLTYPE Release() override {
const ULONG n = --refs_;
if (!n) delete this;
return n;
}
HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* outer, REFIID iid,
void** out) override {
if (!out) return E_POINTER;
*out = nullptr;
if (outer) return CLASS_E_NOAGGREGATION;
return create_value(iid, out);
}
HRESULT STDMETHODCALLTYPE LockServer(BOOL) override { return E_NOTIMPL; }
};
במעגל references שני המונים יכולים להישאר חיוביים גם אחרי שהלקוח החיצוני שחרר הכול. ספירת references אינה collector של מעגלים. בעל החיבור יפעיל Disconnect בזמן שהאובייקטים עדיין חיים, ינתק את הקישורים ואז ישחרר את references החיצוניים. הניתוק צריך להיות בטוח גם בקריאה חוזרת ולא להחזיק lock בזמן Release חיצוני שעלול להפעיל destructor וקוד נוסף.