سه تایپ std::optional، std::variant و std::any که در C++17 به کتابخانه استاندارد اضافه شده‌اند، هرکدام راه‌حلی استاندارد و نوع‌ایمن برای مسائل رایج برنامه‌نویسی ارائه می‌دهند. این تایپ‌ها که تایپ‌های واژگانی (Vocabulary Types) نامیده می‌شوند، به برنامه‌نویس اجازه می‌دهند تا قصد خود را به‌صورت واضح و قابل‌فهم در کد بیان کند. بسیاری از این قابلیت‌ها ابتدا در کتابخانه‌ی Boost وجود داشتند و پس از اثبات کارایی، به استاندارد C++17 راه یافتند.

std::optional: مقدار یا هیچ‌چیز

std::optional برای موقعیت‌هایی طراحی شده که یک مقدار ممکن است وجود داشته باشد یا نداشته باشد. به‌عنوان مثال، تابعی که یک نام را دریافت کرده و سن فرد را از پایگاه‌داده جست‌وجو می‌کند، در صورت پیدا نشدن فرد، نمی‌تواند مقدار معتبری بازگرداند. پیش از C++17، برنامه‌نویسان از مقادیر جادویی مانند -1 یا اشاره‌گرهای nullptr استفاده می‌کردند که هر دو روش خطرناک و مبهم بودند. با std::optional، تابع به‌وضوح یک std::optional<int> برمی‌گرداند که یا حاوی سن است یا خالی (std::nullopt) .

نحوه‌ی استفاده از آن ساده است: پس از فراخوانی تابع، با متد has_value() یا تبدیل ضمنی به bool بررسی می‌شود که آیا مقدار وجود دارد یا خیر. برای دسترسی به مقدار، متد value() مقدار را بازمی‌گرداند (و در صورت خالی بودن استثنا پرتاب می‌کند) و value_or() یک مقدار پیش‌فرض در صورت خالی بودن ارائه می‌دهد. نکته‌ی مهم این است که std::optional هیچ تخصیص حافظه‌ی پویایی انجام نمی‌دهد و مقدار را مستقیماً درون خود ذخیره می‌کند.

std::variant: یکی از چند نوع مشخص

std::variant یک یونیون نوع‌ایمن است که می‌تواند در هر لحظه، مقداری از یکی از انواع تعیین‌شده در یک مجموعه‌ی بسته (Closed Set) را در خود ذخیره کند. این تایپ جایگزین union خطرناک زبان C می‌شود و مدیریت نوع فعال را به‌صورت خودکار انجام می‌دهد. یک کاربرد کلاسیک، تابع تجزیه‌کننده‌ی رشته است که ورودی را بررسی کرده و نتیجه را به‌صورت std::variant<int, double, std::string> برمی‌گرداند؛ اگر رشته عدد صحیح باشد، int ذخیره می‌شود، اگر عدد اعشاری باشد، double و در غیر این صورت، خود رشته .

برای کار با std::variant، روش‌های مختلفی وجود دارد. تابع std::holds_alternative بررسی می‌کند که آیا نوع خاصی در variant فعال است یا خیر و std::get مقدار آن نوع را برمی‌گرداند . با این حال، قدرتمندترین روش، استفاده از std::visit و الگوی ویزیتور (Visitor) است. با این روش، یک شیء قابل فراخوانی (مانند lambda) تعریف می‌کنید که برای هر یک از انواع ممکن در variant، یک رفتار خاص را مشخص می‌کند. std::variant نیز مانند std::optional هیچ حافظه‌ی پویایی تخصیص نمی‌دهد و اندازه‌ی آن برابر با بزرگترین نوع ممکن در لیست خود است.

std::any: هر نوع داده‌ای

std::any برخلاف دو تایپ قبلی، یک مجموعه‌ی باز (Open Set) از انواع را پشتیبانی می‌کند و می‌تواند هر نوع قابل کپی‌سازی را در خود ذخیره کند. این تایپ بیشتر شبیه به متغیرهای var در زبان‌های داینامیک مانند جاوااسکریپت عمل می‌کند. برای ساخت یک std::any، کافی است یک مقدار به آن انتساب دهید و برای دسترسی به مقدار، از std::any_cast استفاده کنید که در صورت عدم تطابق نوع، استثنای std::bad_any_cast پرتاب می‌کند .

با این حال، این انعطاف‌پذیری هزینه دارد. std::any ممکن است برای ذخیره‌سازی مقادیر کوچک از بهینه‌سازی بافر کوچک (Small Buffer Optimization) استفاده کند، اما همچنان اندازه‌ی آن نسبت به دو تایپ دیگر بزرگتر است (در کامپایلرهای مختلف بین ۱۶ تا ۶۴ بایت متغیر است) . همچنین ممکن است نیاز به تخصیص حافظه‌ی پویا داشته باشد که عملکرد را تحت تأثیر قرار می‌دهد. به همین دلیل، قاعده‌ی طلایی این است که تا حد امکان از std::any اجتناب کنید و فقط در شرایطی که نوع داده‌ها کاملاً پویا و از پیش ناشناخته هستند، از آن استفاده کنید.

جدول مقایسه‌ی جامع

 
 
ویژگیstd::optionalstd::variantstd::any
تعداد انواع قابل ذخیرهفقط یک نوع مشخصچند نوع مشخص (مجموعه‌ی بسته)هر نوع ممکن (مجموعه‌ی باز)
نیاز به تخصیص حافظه‌ی پویاخیرخیربله (ممکن است)
اندازه در حافظهکوچک (به‌اضافه‌ی یک بایت پرچم)برابر با بزرگترین نوعنسبتاً بزرگ (۱۶ تا ۶۴ بایت)
دسترسی به مقدار*، ->، value()، value_or()std::get، std::holds_alternative، std::visitstd::any_cast
مدیریت خطابررسی has_value()بررسی با holds_alternative یا std::visitany_cast استثنا یا اشاره‌گر
کاربرد اصلیمقدار ممکن است وجود نداشته باشدیکی از چند نوع مشخصذخیره‌سازی نوع کاملاً پویا

جمع‌بندی نهایی

انتخاب بین این سه تایپ به نیاز شما بستگی دارد. اگر تابع شما ممکن است نتواند نتیجه‌ای بازگرداند، از std::optional استفاده کنید. اگر می‌دانید که خروجی شما یکی از چند نوع مشخص است، std::variant بهترین گزینه است. و اگر نوع داده‌ها کاملاً از پیش ناشناخته و پویا هستند، std::any راه‌حل مناسب خواهد بود، اما با آگاهی از هزینه‌ی عملکردی آن . این تایپ‌ها با افزایش وضوح و ایمنی کد، شیوه‌ی مدرن برنامه‌نویسی در C++ را به سطح جدیدی ارتقا داده‌اند.

کلیدواژه ها : مقایسه std::variant و optional و any-C++17 vocabulary types comparison-کاربرد std::optional-مثال std::variant-آموزش std::any-راهنمای انتخاب variant optional any-std::optional vs std::variant-std::any performance overhead-std::visit practical example-مزایا و معایب std::any-مقایسه تایپ‌های واژگانی-استفاده از std::optional در جستجو-std::variant type safe union-آموزش جامع optional variant any-انتخاب نوع مناسب در C++