رفتن به مطلب
مرجع رسمی سی‌پلاس‌پلاس ایران

فناوری

  • نوشته‌
    20
  • دیدگاه
    6
  • مشاهده
    8,321

مشارکت‌کنندگان این وبلاگ

نهایی‌سازی استاندارد ۲۳ (استاندارد ۲۶ در راه است)

کامبیز اسدزاده

976 بازدید


 با سلام و درود،

پیرو مقالهٔ قبلی در رابطه با بخشی از نهایی‌سازی‌های استاندارد ۲۳

 

در این مقاله قصد داریم در رابطه با استاندارد ۲۶ و نهایی‌سازی‌های ۲۳ یک جمع‌بندی داشته باشیم که بسیار در شناخت و به‌روز رسانی سریع از پیشرفت این زبان را به ما نشان می‌دهد.

cpp26.png

طبق جلسات اخیر از کمیتهٔ استاندارد‌سازی، در اولین جلسه، کمیته بر اصلاح ویژگی‌های C++23 متمرکز شد که عبارتند از:

  • عملگرstatic operator[]
  • ویژگی static constexpr در توابع constexpr 
  • محدودهٔ ایمن range-based در for
  • تعامل std::print با سایر خروجی های کنسول
  • رابط الگوی مونادیک برای std::expected
  • خاصیتstatic_assert (false) و سایر ویژگی‌ها

در جلسهٔ دوم، کمیته بر روی توسعه ویژگی‌های جدید برای C++26 کار کرد، از جمله:

  • ویژگی‌های std::get و std::tuple_size
  • ماکروی #embed
  • بدست آوردن std::stacktrace از استثناء‌ها
  • کرووتین‌های (coroutines) پشته‌ای

در ویژگی‌های سی‌پلاس‌پلاس ۲۳ (static operator[])

تابستان گذشته، کمیته ویژگی static operator() را به استاندارد C++23 اضافه کرد و امکان تعریف operator[] را برای چندین آرگومان فراهم کرد. مرحلهٔ منطقی بعدی دادن فرصت‌های برابر به این عملگرها بود، یعنی اضافه کردن توانایی نوشتنِ static operator[].

enum class Color { red, green, blue };

struct kEnumToStringViewBimap {
  static constexpr std::string_view operator[](Color color) noexcept {
    switch(color) {
    case Color::red: return "red";
    case Color::green: return "green";
    case Color::blue: return "blue";
    }
  }

  static constexpr Color operator[](std::string_view color) noexcept {
    if (color == "red") {
      return Color::red;
    } else if (color == "green") {
      return Color::green;
    } else if (color == "blue") {
      return Color::blue;
    }
  }
};

// ...
assert(kEnumToStringViewBimap{}["red"] == Color::red);

آیا این کد واقعاً کارآمد برای تبدیل رشته به enum است؟

ممکن است تعجب آور باشد، اما کد فوق در واقع بسیار کارآمد است. توسعه‌دهندگانِ کامپایلر از رویکردهای مشابهی استفاده می‌کنند و ما نیز تکنیک مشابهی را در چارچوب userver پیاده‌سازی کرده‌ایم. ما یک کلاس جداگانه به نام utils::TrivialBiMap با رابط‌کاربری راحت‌تر ایجاد کرده‌ایم.

constexpr utils::TrivialBiMap kEnumToStringViewBimap = [](auto selector) {
  return selector()
      .Case("red", Color::red)
      .Case("green", Color::green)
      .Case("blue", Color::blue);
};

راندمان بالا به لطف ویژگی‌ کامپایلرهای بهینه‌سازی مدرن به دست می‌آید (اما هنگام نوشتن یک راه حل کلی باید بسیار مراقب بود). پیشنهاد در سند P2589R1 تمام جزئیات لازم را شرح می‌دهد.

ویژگی static constexpr در توابع constexpr

استاندارد C++23 عملکرد خود را با افزودن constexpr به_chars/from_chars گسترش داده است. اما برخی از اجرا کننده‌گان با مشکل مواجه شدند. چندین کتابخانهٔ استاندارد حاوی آرایه‌های ثابتی برای تبدیل سریع string<>number بودند که به عنوان متغیرهای ثابت در توابع اعلام شدند. متأسفانه، این مانع استفاده از آنها در توابع constexpr شد. این مسئله قابل حل است، اما راه حل ها واقعاً ناشیانه به نظر می‌رسید. در نهایت، کمیته با اجازه دادن به استفاده از متغیرهای static constexpr در توابع constexpr، همانطور که در سند P2647R1 مشخص شد که مشکل را حل کرده‌ است. یک پیشرفت کوچک، اما خوشایند.

 

محدودهٔ ایمن range-based در حلقهٔ for

این احتمالاً هیجان انگیزترین خبری است که از دو نشست جلسهٔ استاندارد‌سازی‌های گذشته منتشر می‌شود! در مورد آن، اجازه دهید با یک معمای سرگرم کننده شروع کنیم: آیا می‌توانید اشکال موجود در کد را شناسایی کنید؟

class SomeData {
 public:
  // ...
  const std::vector<int>& Get() const { return data_; }
 private:
  std::vector<int> data_;
};

SomeData Foo();

int main() {
  for (int v: Foo().Get()) {
    std::cout << v << ',';
  }
}

هرچند پاسخ آن در این‌جا آمده است:

The Foo() function returns a temporary object, and when the Get() method is called on this object, it returns a reference to the data inside the temporary object. The range-based for loop is then transformed into a code close to this one:

    auto && __range = Foo().Get() ;
    for (auto __begin = __range.begin(), __end = __range.end(); __begin != __end; ++__begin)
    {
        int v = *__begin;
        std::cout << v << ',';
    }

Here, the first string is equivalent to this:

    const std::vector<int>& __range = Foo().Get() ;

The result is a dangling reference.

حلقه‌های مبتنی بر محدوده (Range-based for) شامل فرآیندهای زیربنایی زیادی هستند و در نتیجه، این نوع باگ‌ها ممکن است همیشه آشکار نباشند. در حالی که می‌توان این مشکلات را از طریق آزمایش‌های مربوط به sanitizers‌ها به طور مؤثر تشخیص داد، پروژه‌های نوین معمولاً آنها را به‌عنوان روش استاندارد شامل می‌شوند (پروژه‌های مطرحی مانند Yandex، از این قاعده مستثنی نیستند). با این حال، ایده‌آل است که در صورت امکان از چنین اشکالاتی به طور کامل اجتناب کنید. در RG21، ما اولین تلاش خود را برای بهبود این وضعیت چهار سال پیش با سند D0890R0 انجام دادیم. متأسفانه، این روند در مرحله بحث متوقف شد. خوشبختانه، Nicolai Josuttis ابتکار عمل را انتخاب کرد و در C++23، کدهای مشابه دیگر مرجع معلق (dangling reference) ایجاد نمی‌کنند. تمام اشیایی که در سمت راست : در یک حلقه for مبتنی بر محدوده (ranges) ایجاد می‌شوند، اکنون فقط هنگام خروج از حلقه از بین می‌روند. برای جزئیات فنیِ بیشتر، لطفاً به سند P2718R0 مراجعه کنید.

ویژگی std::print

در C++23، یک به‌روزرسانی کوچک اما قابل توجه برایstd::print وجود دارد: خروجی آن برای «همگام‌سازی» با سایر خروجی‌های داده تنظیم شده است. در حالی که بعید است کتابخانه‌های استاندارد در سیستم‌عامل‌های نوین تغییرات قابل توجهی را تجربه کنند، استاندارد به‌روز شده اکنون تضمین می‌کند که پیام‌ها به ترتیبی که در کد منبع ظاهر می‌شوند به کنسول خروجی ساطع می‌شوند:

printf("first");
std::print("second");

 

رابط الگوی مونادیک برای std::expected

یک ویژگی نسبتاً مهم در آخرین لحظه به C++23 اضافه شد: یک رابط مونادیک برای std::expected، مشابه رابط مونادیک که قبلاً برای std::optional موجود بود، اضافه شده است.

using std::chrono::system_clock;
std::expected<system_clock, std::string> from_iso_str(std::string_view time);
std::expected<formats::bson::Timestamp, std::string> to_bson(system_clock time);
std::expected<int, std::string> insert_into_db(formats::bson::Timestamp time);

// Somewhere in the application code...
from_iso_str(input_data)
    .and_then(&to_bson)
    .and_then(&insert_into_db)
    // Throws “Exception” if any of the previous steps resulted in an error
    .transform_error([](std::string_view error) -> std::string_view {
        throw Exception(error);
    })
;

می‌توانید شرح کاملی از تمام رابط‌های مونادیک برای std::expected  را در سند P2505R5 بیابید.

 

خاصیتstatic_assert (false) و سایر ویژگی‌ها

علاوه بر تغییرات قابل توجهی که در بالا ذکر شد، تعداد زیادی بازنگری برای حذف لبه‌های ناهموار جزئی و بهبود توسعه روزمره انجام شده است. به عنوان مثال، فرمت‌کننده‌ها برای std::thread::id و std::stacktrace (P2693 سند) تا بتوان از آنها با std::print و std::format استفاده کرد. به ویژه std::start_lifetime_a بررسی‌های زمان کامپایل اضافی را در سند P2679 دریافت کرده است. قابل توجه است که خاصیت static_assert(false) در توابع الگو (template function) دیگر بدون نمونه‌سازی تابع فعال نمی‌شود، به این معنی که کدی مانند زیر فقط در صورت ارسال نوع داده اشتباه، عیب‌یابی را کامپایل و صادر می‌کند:

template <class T>
int foo() {
    if constexpr (std::is_same_v<T, int>) {
      return 42;
    } else if constexpr (std::is_same_v<T, float>) {
      return 24;
    } else {
      static_assert(false, "T should be an int or a float");
    }
}

علاوه بر تغییراتی که قبلاً ذکر شد، بهبودهای بیشماری در خاصیت (ranges) از C++23 انجام شده است. مهمترین آنها گنجاندن std::views::enumerate در سند P2164 است:

#include <ranges>

constexpr std::string_view days[] = {
    "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
};

for(const auto & [index, value]: std::views::enumerate(days)) {
    print("{} {} \n", index, value);
}

 

ویژگی‌های سی‌پلاس‌پلاس ۲۶

ویژگی‌های std::get و std::tuple_size برای (aggregates) تجمیع

یک ایدهٔ جدید و هیجان انگیز برای بهبود ++C وجود دارد که در حال حاضر به طور فعال در Yandex Go و چارچوب userver استفاده می‌شود و به لطف Boost.PFR برای هر کسی که آن را می‌خواهد در دسترس است.

اگر در حال نوشتن یک کتابخانهٔ مبتنی بر الگو (template) و عمومی هستید، به احتمال زیاد باید از std::tuple و std::pair استفاده کنید. با این حال، برخی از مشکلات در این نوع وجود دارد. اولاً، آنها خواندن و درک کد را دشوار می‌کنند زیرا ورودی‌های نامِ واضحی ندارند، و تشخیص معنای چیزی مانندstd::get<0>(tuple) می‌تواند چالش برانگیز باشد. علاوه بر این، کاربران کتابخانهٔ شما ممکن است نخواهند مستقیماً با این انواع کار کنند و درست قبل از فراخوانیِ روش‌های شما، اشیاء‌ای از این نوع ایجاد می‌کنند که به دلیل کپی کردن داده‌ها می‌تواند ناکارآمد باشد. ثانیاً، std::tuple و std::pair بی‌اهمیت بودن انواعی را که ذخیره می کنند، «تبلیغ نمی کنند». در نتیجه، هنگام ارسال و برگرداندن std::tuple و std::pair از توابع، کامپایلر ممکن است کدی را با کارآیی پایین‌تر تولید کند. با این حال، aggregates (تجمیع‌ها) - ساختارهایی (struct) با میدان‌های عمومی و بدون عملکرد خاص - عاری از اشکالات ذکر شده هستند.

ایده‌ای که پشت سند P2141R0 وجود دارد، این است که با کار کردن std::get و std::tuple_size آنها، امکان استفاده از تجمیع‌ها در کدهای عمومی را فراهم می‌کند. این به کاربران امکان می‌دهد تا ساختارهای خود را مستقیماً بدون کپی برداری غیر ضروری به کتابخانهٔ عمومی شما منتقل کنند. این ایده به خوبی توسط کمیته مورد استقبال قرار گرفت، و ما در آینده روی آزمایش و رسیدگی به هرگونه لبهٔ ناهموار و بالقوه در این زمینه کار خواهیم کرد.

 

ماکروی #embed

در حال حاضر، توسعهٔ فعالی روی یک استاندارد زبان C جدید (یک استاندارد بدون کلاس، بدون ++) وجود دارد که شامل بسیاری از ویژگی‌های مفیدی است که مدت‌هاست در ++C وجود داشته است (مانند: nullptr، auto، constexpr، static_assert، thread_local، [[noreturn]).])، و همچنین ویژگی‌های کاملاً جدید برای ++C. خبر خوب این است که برخی از ویژگی‌های جدید از استاندارد جدید C به C++26 منتقل می‌شوند. یکی از این موارد جدید، #embed است - یک دستورالعمل پیش پردازنده برای جایگزینی محتویات یک فایل به عنوان یک آرایه در زمان کامپایل:

const std::byte icon_display_data[] = {
    #embed "art.png"
};

شرح کامل این ایده در سند P1967 موجود است.

بدست آوردن std::stacktrace از استثناء‌ها

در اینجا، ایدهٔ سند P2370 در WG21 با یک شکست غیرمنتظره روبرو شده است. توانایی به دست آوردن ردیابی پشته از یک استثناء در اکثر زبان‌های برنامه‌نویسی وجود دارد. این ویژگی فوق‌العاده مفید است و به جای پیام‌های خطای غیر اطلاعاتی مانند Caught exception: map::at تشخیص‌های آموزنده‌تر و قابل فهم‌تر را امکان‌پذیر می‌کند که نمونه مثال آن به صورت زیر است:

Caught exception: map::at, trace:
0# get_data_from_config(std::string_view) at /home/axolm/basic.cpp:600
1# bar(std::string_view) at /home/axolm/basic.cpp:6
2# main at /home/axolm/basic.cpp:17

هنگامی که در محیط یکپارچه سازی پیوسته (CI) استفاده می‌شود، این ویژگی می‌تواند فوق‌العاده مفید باشد. این به شما امکان می‌دهد تا به سرعت مسائل را در آزمون شناسایی کنید و از دردسر بازتولید مشکل به صورت محلی اجتناب کنید، که ممکن است همیشه امکان‌پذیر نباشد. متأسفانه کمیتهٔ بین‌المللی به طور کامل از این ایده استقبال نکرد. اما تیم توسعه نگرانی‌ها را بررسی می‌کند و روی اصلاح این ایده کار خواهد کرد تا بتواند حمایت بیشتری را کسب کند.

کسانی که معمولاً می‌پرسند چه تفاوتی بین زبان‌های دیگر و استاندارد‌های سی‌++ وجود دارد، در این‌جا می‌توانند به این موضوع دقت کنند که زبانی مانند سی‌پلاس‌پلاس دارای کمیتهٔ استاندارد‌سازی بین‌المللی است و هر تغییری باید قابل توجیه باشد.

 

کروتین‌های (coroutines) پشته

سرانجام، پس از سال‌ها کار، استاندارد سی‌پلاس‌دلاس به افزودن پشتیبانی اولیه برای برنامه‌های پشته‌ای در C++26 نزدیک شده است (به سند P0876 مراجعه کنید). ارزش آن را دارد که بیشتر در مورد روال‌های پشته‌ای یا بدون پشته بررسی کنیم. برنامه‌های بدون پشته به پشتیبانی کامپایلر نیاز دارند و نمی‌توانند به تنهایی به عنوان یک کتابخانه پیاده‌سازی شوند. از سوی دیگر، کروتین‌های پشته‌ای را می‌توان به تنهایی پیاده‌سازی کرد - برای مثال، با Boost.Context.

در حالت‌های قبلی، تخصیص حافظه کارآمدتر، بهینه‌سازی بالقوه بهتر کامپایلر و توانایی تخریب سریع آنها را ارائه می‌دهد. آنها همچنین در حال حاضر در C++20 در دسترس هستند. ادغام نمونه‌های اخیر در پروژه‌های موجود بسیار آسان‌تر است، زیرا نیازی به بازنویسی کامل در یک اصطلاح جدید مانند برنامه‌های بدون پشته ندارند. در واقع، آنها جزئیات پیاده‌سازی را به طور کامل از کاربر مخفی می‌کنند و آنها را قادر می‌سازند تا کد خطی ساده‌ای را که در لایه‌های زیرینِ ناهمزمانی هستند بنویسند.

بدون پشته (Stackless)

auto data = co_await socket.receive();
process(data);
co_await socket.send(data);
co_return; // requires function to return a special data type

پشته‌ای (Stackfull)

auto data = socket.receive();
process(data);
socket.send(data);

سند  P0876  قبلاً در زیرگروه اصلی قرار داشته است. پس از بحث و گفتگو، تصمیم گرفته شد که مهاجرت این گونه برنامه‌ها (coroutines) بین رشته‌‌های اجرایی ممنوع شود. دلیل اصلی این ممنوعیت این است که کامپایلرها دسترسی به TLS را بهینه کرده و مقادیر متغیرهای TLS را در حافظه پنهان ذخیره می‌کنند:

thread_local int i = 0;
// ...
++i;
foo();  // Stackful coroutines can switch execution threads
assert(i > 0);  // The compiler saved the address in a register; we’re working with the TLS of another thread

جمع‌بندی

در نهایت استاندارد C++23 رسماً به مقامات بالاتر از کمیتهٔ ISO ارسال شده است و به زودی به عنوان یک استاندارد کامل منتشر خواهد شد. در همین حال، توسعه C++26 در نوسان کامل است و چشم‌اندازهای هیجان‌انگیزی برای خاصیت‌های متنوع وجود دارد. اگر ایده‌های نوآورانه‌ای برای بهبود ++C دارید، با خیال راحت آنها را به اشتراک بگذارید. یا - حتی بهتر - ارسال یک پیشنهاد را در نظر بگیرید.



0 دیدگاه


نظرهای پیشنهاد شده

هیچ دیدگاهی برای نمایش وجود دارد.

مهمان
افزودن دیدگاه

×   شما در حال چسباندن محتوایی با قالب بندی هستید.   حذف قالب بندی

  تنها استفاده از ۷۵ اموجی مجاز می باشد.

×   لینک شما به صورت اتوماتیک جای گذاری شد.   نمایش به عنوان یک لینک به جای

×   محتوای قبلی شما بازگردانی شد.   پاک کردن محتوای ویرایشگر

×   شما مستقیما نمی توانید تصویر خود را قرار دهید. یا آن را اینجا بارگذاری کنید یا از یک URL قرار دهید.

  • کاربران آنلاین در این صفحه   0 کاربر

    هیچ کاربر عضوی،در حال مشاهده این صفحه نیست.

×
×
  • جدید...