1.4 - ייצוג אובייקט ואחסון - פתרון
פתרון - אחסון אחד אינו אובייקט אחד לנצח¶
הקוד הבא הוא תוכנית מלאה של שני מחזורי החיים:
#include <cstddef>
#include <cstdio>
#include <memory>
struct Item {
int value;
explicit Item(int n) : value(n) {
std::printf("create %d\n", value);
}
~Item() {
std::printf("destroy %d\n", value);
}
};
int main() {
alignas(Item) std::byte storage[sizeof(Item)];
auto* first = std::construct_at(
reinterpret_cast<Item*>(storage), 11);
std::printf("value=%d\n", first->value);
std::destroy_at(first);
auto* second = std::construct_at(
reinterpret_cast<Item*>(storage), 22);
std::printf("value=%d\n", second->value);
std::destroy_at(second);
}
ה־buffer נשאר קיים לאורך main, אבל שני האובייקטים מתקיימים בזמנים נפרדים. ההשמדה הראשונה אינה משחררת את אחסון המערך, והבנייה השנייה אינה ממשיכה את החיים של האובייקט הראשון.
הקוד אינו מסתמך על כללי שימוש חוזר במצביע הישן. זאת בחירה שמפשטת את ההוכחה: כל גישה לשדה משתמשת במצביע שהתקבל מבניית האובייקט החי הנוכחי.
בדיקת layout¶
הבדיקה הבאה מתאימה למבנה standard-layout:
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <type_traits>
struct Record {
char first;
std::uint64_t number;
char last;
};
int main() {
static_assert(std::is_standard_layout_v<Record>);
std::printf("size=%zu align=%zu offsets=%zu,%zu,%zu\n",
sizeof(Record), alignof(Record),
offsetof(Record, first),
offsetof(Record, number),
offsetof(Record, last));
}
ב־MSVC x64 בתצורת packing רגילה צפוי בדרך כלל גודל עשרים וארבעה, יישור שמונה ו־offsetים אפס, שמונה ושישה־עשר. יש לתעד את התוצאה שהתקבלה בפועל, ובפרט לא לשנות packing בפרויקט בלי לבדוק מחדש את ה־ABI.
גודל uint64_t, כאשר הטיפוס קיים במימוש, הוא בדיוק 64 ביט. המרווחים בין השדות וגודל המבנה הכולל אינם נובעים מכך לבדם. פורמט חיצוני צריך להגדיר גם סדר bytes וגבולות, ולכן לא משדרים padding כחלק ממידע עסקי.
ערך לעומת ביטים¶
אפשר לבדוק את ההבדל כך:
#include <bit>
#include <initializer_list>
#include <cstdint>
#include <cstdio>
#include <limits>
int main() {
static_assert(sizeof(float) == sizeof(std::uint32_t));
static_assert(std::numeric_limits<float>::is_iec559);
for (float n : {1.0f, 2.0f}) {
auto bits = std::bit_cast<std::uint32_t>(n);
std::printf("numeric=%u bits=%08x\n",
static_cast<unsigned>(n),
static_cast<unsigned>(bits));
}
}
הקוד מכליל במפורש את ה־header של רשימת האתחול. גם אם תוכנית מתקמפלת במקרה בגלל הכללה עקיפה דרך header אחר, אין לכך הבטחה שנרצה להסתמך עליה בספרייה.
ההמרה המספרית מחזירה אחד ושתיים. המרת הייצוג מציגה את קידוד ה־float באותה מערכת. ההבדל אינו באג: שתי הפעולות עונות על שאלות שונות.