ویژگی های C++11 و C++14 - هفت خط کد انجمن پرسش و پاسخ برنامه نویسی

ویژگی های C++11 و C++14

+23 امتیاز

c++, آموزش, constexpr, delete, nullptr, lambda, auto, static assert, variadic templates, enum class, initializer list, c++11, c++-faq, using, decltype, کلاس, final, utf-8, mutable, hash table, move-semantics

ویژگی های اضافه شده در C++11 ,C++14 :

  • auto
  • Range Based Loops
  • User-defined Literals
  • Regular expressions
  • Thread support
  • Lambda Functions/Expressions
  • static_assert
  • Enum class
  • decltype
  • nullptr
  • char32_t / char16_t
  • Override
  • Final
  • Default
  • Delete
  • initializer_list
  • constexpr
  • Move Semantics
  • Hash table
  • Variadic templates
  • Non static data member initialize
  • Generic lambas
  • Return type deduction
  • Binary literals
  • Initialize lambda captures
  • Single qoution mark as seperator

 

auto

قبلا برای تعریف متغییر ها حتما باید نوع اون رو مشخص میکردید تا متغییر مورد نظر نسبت به نوع مورد نظر ساخته بشه . ولی گذاشتن کلمه ی auto به جای نوع متغییر میشه باعث میشه که نوع متغییر نسبت به مقداری نوع مقداری که به اون داده بشه بصورت خودکار مشخص بشه و کامپایلر خودش نوع مناسب رو برای متغییر در نظر میگیره .

مثلا :

list<int> numbers;
list<int>::iterator i = numbers.begin();

auto j = numbers.begin();
auto k = 1;
auto m = some_func();

در کد بالا متغییر i رو از نوع list<int>::iterator تعریف کردیم برای اینکه باید از نوع برگشتی () numbers.begin باشه و کاملا هم درسته .

خوب بجای این نوشتن همچین نوعی برای متغییر میتونیم به جاش از auto استفاده کنیم تا خود کامپایلر نوع رو تشخیص بده و دیگه نیازی به این نباشه که ببینیم () numbers.begin چه نوعی رو برمیگردونه نداریم و نوع متغییر خودمون رو نسبت به اون تعریف کنیم .

که برای متغییر j همچین کاری رو کردیم .نوع متغییر i رو خودمون مشخص کردیم و نوع متغییر j رو کامپایلر مشخص کرده . درواقع متغییر j دقیقا هم نوع متغییر i هست .

متغییر k هم بدلیل اینکه مقدار 1 رو بهش دادیم کامپایلر اون رو به int تبدیل میکنه .

و متغییر m هم همون نوعی میشه که تابع () some_func برمیگردونه .

چندتا نکته در مورد auto :
متغییری که بصورت auto تعریف میشه و برای مقدار دهی اولیه مقداری بهش داده میشه . ولی هیچ ويژگي مانند Const یا Reference رو از مقدار داده شده رو نمیگیره .تنها از نوع اون متغییر یا مقدار-لفظی الگو میگیره و نوع خودش رو دوباره تعریف میکنه .
نمونه :

const int i = 3;
const int& t = i;
auto r = t;
r++;

در اینجا r تغییرپذیر است چون متغییر r تنها int رو از متغییر t الگو گرفته . نه const و reference رو .این به این معنی است که متغییر r تنها یک int معمولی است .

نکته دیگه اینه که اگه شما auto رو بصورتی یک ارجاع تعریف کنید همه ی ویژگی های متغییر سمت راست رو میگیره .یعنی خود این auto از نوع ارجاع میشه .

int t = 0;
auto& r = t;
r++;

 



اگر هم متغییر سمت راست اشاره گر باشه . auto همه ی ویژگی های سمت راست رو میگیره.

const int a = 0;
const int* b = &a;
auto& r = b;
*r = 10;

اگر r رو تغییر بدید با خطای تغییر دادن const رو برو میشید .

 

Range Based Loops

یکی از ویژگی های بدرد بخور C++‎‎‎‎‎‎‎‎ 11 همین range-based loops هست .
این حلقه همون حلقه for هست با این تفاوت که دامنه ای محدود داره و برای شمردن های خاص بکار مبیره .

برای نمونه این تکه کدی هست که با for معمولی نوشته شده :

int list[]={1,2,3,4,5,6,7,7,7,7,7,8,9,0,0,0,1,1};
 
for(int i = 0;i < (sizeof(list) / sizeof(int));i++)
{
    cout<<list[i];
}

حالا این همون کد با range-based for نوشته شده :

int list[]={1,2,3,4,5,6,7,7,7,7,7,8,9,0,0,0,1,1};
 
for(int i:list)
    cout<<i;

این هم یه روش دیگه :

for(auto i:{1,2,3,4,5,6,7,7,7,7,7,8,9,0,0,0,1,1})
    cout<<i;

i یک نوع هست . میتونه int باشه میتونه float باشه . میتونه هر نوع دیگه ای باشه .
تعداد تکرار این حلقه به اندازه ی تعداد اعضا (Member) های این آرایه است .در کد بالا 18 بار این حلقه میچرخه.چونکه تعداد اعضای آرایه ی list میشه 18 تا .
و در هر گام for مقدار بعدی ریخته میشه درون متغییر i .
یعنی در اولین گام حلقه for مقدار i میشه 1. و کد درون for انجام میشه .
و در دوازدهمین گام حلقه for مقدار i میشه 8 . چون دوازدهمین عنصر این آرایه مقدار 8 هست.
برای نمونه اگه i از نوع float باشه . در اینجا یه تغییر کوچیک انجام میگیره و مقدار 8 در متغییر i که از نوع float هست ریخته میشه .
مناسب ترین روش برای این حلقه ها بکارگرفتن از auto هست .
اگه i رو از نوع auto تعریف کنیم Compiler نوع i رو از نوع عنصر داده شده تعریف میکنه. که اینجوری خیلی کار آسونتر و بهتر میشه .

برای مثال اگه میخواهید یه vector رو مقدار دهی کنید ، میتوانید بجای این روش :

vector<int> vec;
 
vec.push_back(10);
vec.push_back(3);
vec.push_back(5);
vec.push_back(17);
vec.push_back(1);
vec.push_back(6);
vec.push_back(2);
vec.push_back(2);

از این روش میشه استفاده کرد :

for(auto i:{10,3,5,17,1,6,2,2})
    vec.push_back(i);

User-defined Literals

توسط  BlueBlade

 

توانایی تعریف literal های جدید مشایه literal های پش فرض :

123 // int
1.2 // double
1.2F // float
'a' // char
1ULL // unsigned long long
0xD0 // hexadecimal unsigned
"as" // string

مثال :

#include <iostream>>
#include <vector>
#include <cmath>
#include <stdlib.h>
using namespace std;
 
vector <int >  operator "" _vect(unsigned long long  num)
{
    vector <int > v;
    while(num>0)
    {
        v.push_back(num%10);
        num /=10;
    }
    return v ;
}
int operator "" _pow (const char *str,size_t n)
{
    return  pow(atoi(str),2);
}
 
string operator "" _cpp(const char ch)
{
    return "C++‎‎‎11 is fun:D";
}
 
int main()
{
   vector <int > vect=12345678_vect;
 
   for(auto i:vect)
       cout<<i<<endl;
 
   cout<<"12"_pow<<endl;
   cout<<'m'_cpp<<endl;
}

 

Lambda Functions/Expressions

یکی از ویژگی های جدید و برجسته ی C++‎‎‎‎‎‎‎‎‎‎‎ 11 توابع ها و عبارت های لامبدا (Lambda) هست .

Lambda ها توابعی هستند بدون نام ! که میتونن درون و بیرون توابع معمولی تعریف و پیاده سازی بشند.
توابع Lambda از این بخش ها تشکیل میشن :
 

  • Capture
  • Arguments
  • Mutability
  • Return Type
  • Body
  • Parameters


برای لامبدا ها چگونگی تعریف کردن یکسانی نیست . بسته به نیاز تعریف کردنشون هم متفاوت هست .
ولی رویه هم رفته میشه لامبدا ها رو به این شکل نشون داد :

[] () mutable -> {} ()


[] : این برای اعلان کردن یک عبارت لامبدا بکار میره .
() : در این پرانتزها آرگومان های لامبدا گذاشته میشه
mutable : بسته به شرایط است. اگر mutable نوشته شود ، لامبدا میتونه مقدارهایی رو که بصورت pass-by-value رو از حوزه ی (Scope) بیرون Capture کرده رو تغییر بده . و یا مثلا اگر یک شی از یک کلاس رو Capture کرده که و میخواد یکی از متد های اون شی رو صدا بزنه و اون متد const نیست و باعث تغییر عضو های کلاس میشه باید لامبدا رو mutable تعریف کنیم .
<- : پس از این عملگر نوع برگشتی لامبدا نوشته میشه .
{} : بدنه ی لامبدا درون این تعریف میشه .
() : این پرانتز در پایان لامبدا نشوته میشه که برای وارد کردن پارامتر هست .

نمونه هایی از لامبدا :

auto a = []{return 5;}();
auto b = []{return 'a';}();
int a = 2,b = 7;
auto r = [](int a,int b){if(a > b)return a;if(b > a)return b; return 0;}(a,b);

 

auto jam = [](int n1, int n2, int n3){ return n1+n2+n3; } (10,20,70);

در نمونه های بالا نوع برگشتی تعریف نشده . ولی بصورت خودکار بسته به مقداری که return برمیگردونه مشخص میشه .

در این نمونه نوع برگشتی رو double درنظر میگیریم و برای اینکار از عملگر <- بکارگیری میکنیم :

auto aa = [](double f)->double { float ret ; ret = f * 10;return ret;}(44.0f);

یه نکته اینه که برای صدا زدن یک عبارت لامبدا باید پرانتز های پایانی رو استفاده کنید. اگر لامبدا پارامتر میخواست پارامتر رو میدیم ، اگرهم نه بصورت خالی () باید نوشته بشه .


دخیره کردن Lambda برای استفاده دوباره .
برای ذخیره کردن و صداکردن یک لامبدا به این شکل میشه این کار رو کرد :

auto aa = [](void) {cout<<"Hi"<<endl;return;};
aa();

یک نکته هم در مورد این هست . شما میتونید همین تعریف بالایی رو بیرون از توابع هم بکنید . و در توابع دیگه میتونید aa رو صدا بزنید .

یه مثال کاربردی برای مرتب سازی اعداد :

std::vector<int> vec_(10);
 
    for (int i=0;i < 10;i++)
        vec_[i]= rand()%10;
 
    sort(vec_.begin(),vec_.end(),  [](const int & a, const int & b) -> bool
{
    return a < b;
});
 
    for (auto a:vec_)
        cout <<a << endl;

بخش Capture عبارت های Lambda :

بخش Capture مشخص میکنه که چه جور متغییر هایی که از حوزه های بالاتر هستند گرفته ( Capture ) بشن .
یعنی اگر شما بخواهید از متغییرهایی که بیرون از عبارت لامبدا تعریف شدن استفاده کنید ، اول باید اونها رو Capture کنید تا بتونید درون لامبدا ازشون استفاده کنید .
درصورتی که لامبدایی رو که تعریف میکنید به این شکل [] باشه ، هیچ متغییری رو از بیرون Capture نمیکنه .

اگر شما نام متغییری رو بیارید که میخواهید اون رو Capture کنید ٰ، یه کپی از اون متغییر درست با همون نام درون لامبدا ایجاد میشه . که نمیتونید تغییرش بدید و فقط خواندنی هست .
درصورتی میتونید متغییر Capture شده ای که کپی آن در لامبدا ایجاد شده رو تغییر بدید که لامبدا رو با ویژگی mutable بشناسونید.

الگو های Capture (ربودن) :

  • [] : هیچ چیز رو Capture نمیکنه .
  • [=] : همه چی رو بصورت مقدار Capture میکنه . یعنی کپی میکنه درون خود لامبدا با همون نام ها .
  • [&] : همه چیر رو بصورت ارجاع Capture میکنه . یعنی اگر شما تغییرش بدید ، متغییری که به اون ارجاع شده هم تغییر میکنه.
  • [var] var نام متغییری است که میخواهید Capture بشه بصورت مقدار .
  • [var&]  var نام متغییری است که میخواهید Capture بشه بصورت  ارجاع .

نمونه :

int a = 5,b = 10;
[a] (void)
{
    a++;
    b++;
}();

متغییر a حالا Capture شده . ولی تغییرپذیر نیست . چون که ما Lambda رو بصورت Mutable تعریف نکردیم تا بتونه متغییر رو تغییر بده .
متغییر b هرگز Capture نشده . درکل نمیشه ازش استفاده کرد .
اگر تو همین مثال ما Lambda رو از نوع Mutable تعریف میکردیم ، میتونستیم a رو تغییر بدیم . ولی متغییر a اصلی که بیرون Lambda هست تغییر نمیکنه. چون متغییر aی که درون Lambda هست بصورت مقدار Capture شده.
و باز از b نمیتونیم استفاده کنیم چون هرگز Capture نشده .

یه نمونه دیگه :

int a = 5,b = 10,c = 15;
 
auto my = [=] (void) -> float
{
    return a + b + c;
}();

علامت = در داخل بخش Capture به این معنی است که همه ی متغییر ها رو بصورت مقدار Capture کن .
ولی اگر شما بخواهید تغییری درونشون بدید باید Labmda رو با ویژگی Mutable تعریف کنید . در اینجا عبارت Lambda تنها متغییر ها رو جمع میکنه و مقدار رو برمیگردونه و هیچ تغییری نمیده.

نمونه دیگه :

int a = 5,b = 10,c = 15;
 
auto pishfarz = [&](void) -> void
{
    a = 1;
    b = 2;
    c = 3;
};

تو این نمونه علامت & داخل بخش Capture میگه که همه چیز رو بصورت یک ارجاع Capture کن .
و حالا ما این Lambda رو صدا نمیزنیم . ولی اون رو میزاریم درون متغییر Pishfarz .هر زمان خواستیم میتونیم متغییر Pishfarz رو با 2 پرانتز ()‌ صدا بزنیم تا Lambda ی که درون اون هست انجام بشه .
و حالا شما اگه هرچیز روکه بصورت یک ارجاع Capture شده رو تغییر بدید ، متغییر اصلیش هم تغییر میکنه .
و برای تغییر دادن متغییرهای ارجاع نیازی نیست که Lambda رو با ویژگی Mutable بشناسونیم.
Mutable تنها برای تغییر متغییر های Capture شده بصورت مقدار هست .

نکته ی دیگه ای که در مورد Capture ها هست اینه که شما میتونید اونها رو باهم ترکیب کنید .
نمونه :

[a,b]() {return a+b;}();

متغییر a و b رو بصورت مقدار Capture میکنه .

int a = 0;
int b = 5;
 
int jam = 0;
[=, &jam](){ jam = a+b;}();

همه ی متغییر ها رو بصورت مقدار Capture میکنه ،‌ولی متغییر jam رو بصورت ارجاع Capture میکنه.

در Capture های ترکیبی & و = باید اول نوشته شوند وگرنه با خطا روبرو میشید .

[&jam,=]{} // nadorost .  = baiad samte chap bashe.
[a1,a2,&]{} // nadorost . & baiad samte chap bashe.

در لیست Capture کردن نمیشه این عبارت رو نوشت :‌

[=, &]{} // nadorost
سوال شده فروردین 27, 1393  بوسیله ی Ali Rahbar (امتیاز 4,240)   6 16 46
ویرایش شده بهمن 16, 1393 بوسیله ی BlueBlade

4 پاسخ

+13 امتیاز

static_assert

مثل assert در assert.h عمل میکنه با این تفاوت که این یکی فقط برای زمان کامپایل هست نه زمان اجرا .
مثال :

#include <type_traits>
 
template <typename Type> class Matrix4x4
{
    static_assert(std::is_floating_point<Type>::value, "Invalid Type !!!");
    Type m[4][4];
};
 
template < typename Type, size_t Length> class StaticArray
{
    static_assert(Length <= 4096, "Invalid Length !!!. Max Array Length == 4096");
    Type array[Length];
};
 
int main(int argc)
{
    Matrix4x4<float> matrix1; //ok
    Matrix4x4<double> matrix2; //ok
    Matrix4x4<char> matrix3; //error !!! invalid type
    Matrix4x4<bool> matrix4; //error !!! invalid type
 
    StaticArray<int, 128> arr1;    //ok
    StaticArray<int, 20000> arr2; // error !!! Invalid Length
}

Enum class

توسط  BlueBlade

اضافه شدن ساختار کاربردی و قدرتمند جدیدی که معایب enum قدیمی را ندارد
نبود scope در enum

Enum Games{Taken,COD,Fast Five};
Enum Movies{Taken,Fast Five}//error

این کد ارور redefinition of Taken ,Fast Five رو میده.
که برای حل این مشکل توی C++‎‎‎‎‎‎‎‎‎‎‎‎‎‎98 مجبور بودین از namespace یا چیزای دیگه استفاده کنین به این شکل

namespace Game {
enum Game {FastFive, BattleField, Taken};
}
 
namespace Movie {
enum Movie {FastFive,GodFather, Taken};
}

که این کد هم باز تمام مشکلاتو حل نمی کرد
شما الان میتونین Movie رو با Game مقایسه کنین به این شکل !

if(Game::Game::Taken==Movie::Movie::Taken)
       cout<<"OMG!  Movie = Game  in C++‎‎‎‎‎‎‎‎‎‎‎‎‎‎98 !";

که برای حل این مشکل منطقی مجبور بودین enum رو توی کلاس تعریف کنین که تعداد خطوط کد برای این کار ساده خیلی زیاد میشد و خوانایی کد رو هم به شدت میاره پایین و بعضی مواقع سرعت اجرا رو هم میاره پایین !

در C++‎‎‎‎‎‎‎‎‎‎‎‎‎‎11 میتونین به جای استفاده ازenum معمولی که مشکلاتی مثل همون چیزی که گفتم داره از enum class استفاده کنین که مثل کلاس ها scope دارن برای مثال

enum class Game{FastFive, BattleField, Taken};
enum class {FastFive,GodFather, Taken};//bar khalaf enum in yeki error redifinition ro nemide
if(Game::Taken==Movie::Taken)//bar khalaf enum error no match for 'operator==' (operand   types are 'Game' and 'Movie')  mide va joloye compile ro migire
        cout<<"In ghesmat hich vaght ejra nemishe";

و توی enum class تبدیل از enum به integer نداریم که جلوی خیلی از مشکلات منطقی رو می گیره!

Game::Game game;
   if(game>100)//ok
       cout<<"convert enum to int ! C++‎‎‎‎‎‎‎‎‎‎‎‎‎‎98";
   Game gamec11;
   if(gamec11>100)//error can not compare
       cout<<"Ejra nemishe hich vaght ! C++‎‎‎‎‎‎‎‎‎‎‎‎‎‎11";

یه برتری دیگه هم که به  enum class ها افزوده شده اینه که شما میتونید اندازه یا نوع enum رو مشخص کنید.

این یه نمونه از enumیه که از نوع unsigned char هست :

enum Jahat : unsigned char
{
    Chap = 0,
    Rast,
    Bala,
    Payin,
    Aqab,
    Jelo,
};

وقتی یه نمونه از enum ساخته بشه اندازه ی نمونه ساخته شده ۱ بایت هست . بخاطر اینکه enum از نوع unsinged char هست .
برای مثال اگر نوع رو unsigned long مینوشتیم ، نوع و اندازه ی enum بسته به همون unsigned long تغییرمیکرد و اندازش هم ۴ بایت میشد.

 

یکی دیگه از مشکلات enum ها در نسخه های قبلی این بود که نمی شد اونها رو به صورت forward declaration تعریف کرد.
این مشکل بعضی وقتا باعث میشد ادم مجبور بشه enum ها رو در یک سرفایل دیگه بنویسه .
خوشبختانه این مشکل هم حل شده .

مثال :

enum class eColor : char;    //forward declaration
 
struct Object
{
    eColor color;
    //...
};
 
enum class eColor : char
{
    BLACK, WHITE, RED, GREEN, BLUE
};

decltype

این کلمه کلیدی جدید دیگه ای که افزوده شده ، کاربردش خیلی شبیه به auto هست .
مانند typeid هم هست . ولی typeid فقط اطلاعات های وابسته به اون نوع رو برمیگردونه . و شما نمیتونید باهاش یک متغییر رو تعریف کنید . نیاز به Run-time Type Info هم داره .
ولی با decltype میشه نوع یک عبارت و متغییر رو بدست آورد جوریکه بشه از همون نوع بدست آمده یک نمونه یا متغییر جدید هم درست کرد .

int a = 0;
decltype(a) b;
 
float c = 0;
decltype(c) d;
 
int main()
{
    auto h = 999665.2532/14;
    decltype(h) a1,b1,c1,d1,e1,f1;
}

nullptr

کلمه ی nullptr هم سازمانی شده ی NULL هست .
برای مقدار دهی به اشاره گر هایی که هنوز مقدار درستی ندارند ، بهتره به جای NULL از nullptr استفاده کرد .
یه نکته اینه که شما میتونستید برای نمونه به یک متغییر از نوع int رو هم بهش مقدار NULL رو بدید .ولی در مورد nullptr این درست نیست . nullptr تنها برای اشاره گر ها بکاربرده میشه.

 

char16_t و char32_t

در C++‎‎‎‎‎ 11 برای ذخیره کردن نویسه های Unicode از این نوع داده ها بکارگیری میشه . char32_t برای UTF-32 و char16_t برای UTF-16 . برای UTF-8 هم از خود char بکارگیری میشه .
برای نوشتن رشته های Unicode بصورت لفظی هم به این شکل میشه این کار رو کرد .

auto a = U"This is UTF-32 String";
auto b = u"This is UTF-16 String";
auto c = u8"This is UTF-8 String";

U : نویسه ی یو بزرگ برای رشته ی UTF-32 بصورت لفظی هست . در آخرهم بصورت آرایه ای از const char32_t ذخیره میشه .
u : نویسه یو کوچیک هم برای رشته ی UTF-16 بصورت لفظی هست . بصورت آرایه ای از const char16_t ذخیره میشه .
u8 : برای رشته های UTF-8 هست . بصورت آرایه ای از const char ذخیره میشه .

اگه میخواهید از Encoding ویژه ای بکارگیری کنید میتونید از User-defined literals بکارگیری کنید تا String Literal خودتون رو پیاده سازی کنید .

کامپایلر GCC/MinGW تو ویرایش 4.8.1 تونسته همه ی ویژگی های C++‎‎‎‎‎ 11 رو پیاده سازی و پشتیبانی کنه .
کامپایلر LLVM Clang 3.3 هم همینطور .

برای اینکه C++‎‎‎‎‎ 11 رو توی GCC فعال کنید ، باید از std=c++11- بکارگیری کنید .

پاسخ داده شده فروردین 27, 1393 بوسیله ی Ali Rahbar (امتیاز 4,240)   6 16 46
ویرایش شده فروردین 30, 1393 بوسیله ی BlueBlade
+10 امتیاز

Override

همیشه در کلاس Derived برای Override کردن یک تابع مجازی از کلاس Base به این صورت انجام میشه :

include <iostream>
#include <conio.h>
 
using namespace std;
 
struct A
{
    virtual void print (long var)  { cout<<"A::print() - "<<var <<endl; }
};
 
struct B : A
{
    virtual void print (long var)  { cout<<"B::print() - "<<var <<endl; }
};
 
int main()
{
    A* a = new A;
    B* b = new B;
    a->print(5);
    b->print(10);
 
    a = (A*) b;
    a->print(5);
 
    getch();
}

تو کد بالا تابع () B::print بر روی تابع ()A::print جایگزین میشه و تابع () A::print رو Override میکنه .
ولی اگر شما برای مثال آرگومان تابع ()A::print رو از long به float یا هر چیزی دیگه ای تغییر بدید تابع ()A::print بدست تابع ()B::print دیگه Override نمیشه و کلاس B متد ()A::print رو به ارث میبره و  تابع ()B::print هم داخلش میمونه. و شما نمیفهمید . برای همین میتونه درون برنامه مشکلات زیادی بوجود بیاد .
در C++‎‎‎‎‎‎‎‎‎11 ویژگی به نام override افزوده شده برای اینکه شما این رو به تابع جدیدی که میخواهید تابع قبلی رو Override کنه میدید، و با این کار Compiler رو مجبور میکنید که این تابع باید تابع یکسان خودش در کلاس Base رو Override کنه .
بدون این ؛ Compiler زمانیکه میبینه که تابع همنام ؛ ولی با آرگومان های متفاوت در کلاس Base وجود داره ، تابع جدید شما رو بر روی اون Override نمیکنه ، چون نمیدونه خواست کاربر از تعریف کردن تابع  جدید چیه ! برای همین تنها تابع جدید شما رو به کلاستون اضافه میکنه ، تابع قبلی رو هم که تنها آرگومان هاش متفاوت بوده رو نگه میداره ، و کلاس شما اون رو از کلاس Base به ارث میبره .

چاره اینه که شما ویژگی override رو به تابع ای بدید که میخواهید که به جای تابع کلاس Base گذاشته بشه .
و Compiler زمانی که تابع شما رو ببینه ، به دنبال همچین تابع ای در کلاس Base میگرده که اونو Override کنه . حالا که به احتمال در آرگومان های تابع کلاس Base تغییر پدید اومده ، Compiler تابع همنام و هم آرگومان تابع ی شما رو پیدا نمیکنه و نمیزاره که تابع شما تعریف بشه و شما با خطا روبرو میشید تا پی ببرید که تابع شما توانایی Override کردن تابع کلاس Base رو نداره .
به این شکل از اعلان شدن 2 تا تابع متفاوت بجای 1 تابع Override شده جلوگیری میشه .
یه نکته هم اینه که با این که اگر تابع کلاس Base ویژگی مانند const رو داشته باشه ولی تابع کلاس Derived اون رو نداشته باشه ، Override انجام نمیشه .
پس همیشه توابعی که قصد Override کردن رو دارند خیلی بهتره که با خصوصیت override اعلان بشنوند تا از خیلی مشکلات جلوگیری بشه .

در اینجا مشکلی پیش نمیاد چون تابع ها با هم یکسان هستند :

struct A
{
    virtual void print (long var)  { cout<<"A::print() - "<<var <<endl; }
};
 
struct B : A
{
    virtual void print (long var) override  { cout<<"B::print() - "<<var <<endl; }
};

ولی تو اینجا مشکل اینه که Compiler تابع یکسانی رو برای Override کردن در کلاس Base پیدا نمیکنه چونکه آرگومان های تابع کلاس A با تابع کلاس B متفاوت هست . و کاربر با خطا روبرو میشه تا از این مشکل اطلاع داشته باشه .

struct A
{
    virtual void print (float var)  { cout<<"A::print() - "<<var <<endl; }
};
 
struct B : A
{
    virtual void print (long var) override  { cout<<"B::print() - "<<var <<endl; }
};

 

Final

اگر تابع بخواد که همیشه Override نشدنی باشه، باید با ویژگی final اعلان بشه .

struct A
{
    virtual void print (long var) final  { cout<<"A::print() - "<<var <<endl; }
};
 
struct B : A
{
    virtual void print (long var) override  { cout<<"B::print() - "<<var <<endl; }
};

تو کد بالا با اینکه جفت توابع با هم یکسان هستند ، ولی چونکه ویژگی final در تابع کلاس Base هست امکان Override شدنش نیست ، و کاربر با خطا روبرو میشه .

از ویژگی final میشه class ها و struct ها رو غیر قابل مشتق شدن کرد .
یعنی اگر شما جلوی struct/classی ویژگی final رو بنویسید هیچ کلاسی نمیتونه از این کلاس مشتق بشه و چیزی رو به ارث ببره . اگر همچین عملی انجام بشه کاربر با خطا روبرو میشه .

struct A final
{
    virtual void print (long var)   { cout<<"A::print() - "<<var <<endl; }
};
 
struct B : A
{
    virtual void print (long var)   { cout<<"B::print() - "<<var <<endl; }
};

 

Default


به شکل پيش فرض ; Compiler متدهای ( = Default Constructor ، Destructor ، Copy Constructor ، Operator ) برای هر کلاس بصورت Implicit پیاده سازی میکند .

برای مثال بدون اینکه شما این توابع رو اعلان و پیاده سازی بکنید از اون ها بکارگیری میکنید :

class A
{
public:
    int a,b;
 
};
 
int main ()
{
    A a;
    A b;
    a = b;
    A c(a);
    A* t = new A;
 
    getch();
}

حالا اگر شما تابع سازنده رو فقط اعلان کنید و پیاده سازی نکنید . تو کد بالا با مشکل روبرو میشید که از تابع () A::A بکارگیری شده ولی پیاده سازی نشده.
مانند این :

class A
{
public:
    int a,b;
    A();
 
};
 
int main ()
{
    A a;
    A b;
    a = b;
    A c(a);
    A* t = new A;
 
    getch();
}

بهرحال شما میتونید با پیاده سازی توابع خودتون اون ها رو با توابعی که خود Compiler تعریف کرده جایگزین کنید.

ویژگی default وابسته به توضیح بالا هست.
ویژگی default میتونه از یکی از این چهار تابع بالا که نشان داده شده بشه .
درواقع ویژگی default این رو موضوع رو به استفاده کننده کلاس میرسونه که این تابع درسته که اعلان ( Declare ) شده ولی پیاده سازیش همون پیاده سازیه پیش فرض Compiler هست نه پیاده سازیه کسی که کلاس رو نوشته ، تا استفاده کننده از نهوه ی کارکرد این تابع آگاه بشه .
کد اولی کارکردش با این کد یکی هست :

class A
{
public:
    int a,b;
    A() = default;
    ~A() = default;
    A(const A&) = default;
    A& operator = (const A&) = default;
};
 
int main ()
{
    A a;
    A b;
    a = b;
    A c(a);
    A* t = new A;
 
    getch();
}

اگر شما این توابع رو اعلان کنید و روبرو این توابع ویژگی default رو ننویسید ، باید اون رو پیاده سازی کنید . ولی با ویژگی default پیاده سازی ، همون پیاده سازی پیش فرض خود Compiler خواهد بود .

 

Delete

با ویژگی چشمگیری مانند delete در زمان اعلان کردن توابع میتونید از پیاده سازی شدن اونها جلوگیری کنید .

class A
{
public:
    A() = delete;
    int a,b;
};
 
int main ()
{
    A a;
    A b;
    a = b;
    A c(a);
    A* t = new A;
 
    t->Foo();
 
    getch();
}

تو این کد سازنده پیش فرض با ویژگی delete اعلان میشه و پاک میشه .
پس زمانی که یه شی از کلاس A میخواد ساخته بشه سازنده صدا زده میشه , ولی اون حذف شده ، و استفاده کننده کلاس آگاه میشه که نباید از این روش استفاده کنه .
یکی از مواردی که از این ویژگی در اون بکارگیری میشه اینه که بوسیله این ویژگی با پاک کردن توابع operator = و (&A(const A یک کلاس رو کپی نشدنی کرد ، یا مواردی مانند این .

 

initializer_list

توسط  BlueBlade

کلاس فوق العاده کاربردی که در C++‎‎‎‎‎‎11 اضافه شده شما فرض کنید قبلا می خواستین یک وکتور بسازین بعد چند تا مقدار اولیه بهش بدین مجبور بودین این کارو بکنین

int tmp[] = { 10, 20, 30 };
std::vector<int> v( tmp, tmp+3 );

یا این که بیاین ۳ تا push_back بزارین
ولی الان در C++‎‎‎‎‎‎11 به راحتی می تونین همچین کدی بنویسین

vector<int> a={1,2,3,4};

برای وکتور 2 بعدی

vector<vector<int>> a={{1,2,3,4},{1,2}};

برای مپ

map<string,int>a ={ {"omid",1},{"ali",2},{"reza",3}};

برای لیست ۲ بعدی

list<list<int>> a={{1,2,3,4},{1,2}};

و...
حتی شما می تونین از intializer_list برای ساختن constructor کلاس هم استفاده کنید به این شکل

#include <iostream>
#include <vector>
using namespace std;
class MyClass
{
private :
     vector <int>values;
public:
    MyClass(initializer_list <int> a)
    {
        for(auto i :a)
        {
            cout<<i;
        }
        values=a;
 
    }
};
 
int main()
{
    MyClass myClass({1,2,3,4});
 
}

یا حتی میشه از template برای حالت کلی تر استفاده کرد یا به فانکشن فرستاد

#include <iostream>
using namespace std;
 
class MyClass
{
public:
    template <class T>
    MyClass(initializer_list <T> init_list)
    {
        for(auto i :init_list)
        {
            cout<<i;
        }
        cout<<endl;
    }
 
};
void exampleFunction(initializer_list <int> a)
{
       for(auto i :a)
        {
            cout<<i;
        }
        cout<<endl;
}
int main()
{
    MyClass myClass ({1,2,3,4});
    MyClass myClass2 ({"a","b","c"});
    exampleFunction({1,2,3,4});
}

constexpr

منبع

عبارت هایی هستند مانند 3+4 که هم در Compile time و هم در Run Time یه نتیجه رو میدند .
عبارت هایی که با constexpr اعلان میشن ، توانایی رو برای بهینه سازی کردن به Compiler میدن . Compiler ها به صورت تکراری از اینها در Compile Time بکارگیری میکنند و کد پایانی برنامه رو Hardcode میکنند .( نبیجه اعداد یا خود اعداد رو درون کد پایانی میگزارن .)

یه عبارت ثابت نمیتونه یک تابع یا سازنده ی یک شی رو صدا بزنه .
این کد درست نیست :

int get_five() {return 5;}
  
int some_value[get_five() + 7]; // Create an array of 12 integers. Ill-formed C++‎‎‎‎‎‎

این در C++‎‎‎‎‎‎03 درست نیست . چونکه get_five() + 7 یک عبارت ثابت نیست . یک کامپایلر C++‎‎‎‎‎‎03 هیچ راهی برای فهمیدن به این نداره که آیا ()get_five در Run-Time ثابت هست یا نه . این تابع میتونه یک متغییر سراسری رو تغییر بده و یا توابع  ثابت دیگری که Run-Time نیستند رو صدا بزنه و دیگر کارها ...

کلمه ی کلیدی constexpr به C++‎‎‎11 افزوده شده تا اجازه بده کاربر بتونه اعلان کنه که تابع یا سازنده ی شی یک ثابت Compile-Time است .
نمونه ی بالا رو میشه به این صورت درست کرد :

constexpr int get_five() {return 5;}
  
int some_value[get_five() + 7]; // Create an array of 12 integers. Legal C++‎‎‎‎‎‎11

این کامپایلر رو آگاه میکنه که () get_five یک عبارت ثابت Compile-time است.

بکارگیری از constexpr برای تعریف کردن یک تابع باعث این میشه تا یکسری محدودیت به اون تابع داده بشه .
1 :‌ تابع نمیتونه نوع برگشتیه void رو داشته باشه.
2 : درون بدنه ی تابع امکان اعلان کردن متغییر یا تعریف نوع داده ای تازه وجود نخواهد داشت .
3 : بدنه تابع تنها میتونه دارای اعلان ها ، Null-Statements و تنها یک دستور برگشتیه return باشد .

پیش از C++‎‎‎‎‎‎11 فقط مقدار متغییر هایی میتونستند در عبارت های ثابت کاربرده شوند که بصورت ی const تعریف میشدند که بدست یک عبارت ی ثابت مقدار دهی اولیه میشدند و از نوع اعداد صحیح یا Enum بودند . C++‎‎‎‎‎‎ 11 این محدودیت رو که متغییر ها باید اعداد صحیح باشند یا Enum رو از بین میبره، ولی اونها هم باید با واژه ی کلیدیه constexpr اعلان شوند .

constexpr double earth_gravitational_acceleration = 9.8;
constexpr double moon_gravitational_acceleration = earth_gravitational_acceleration / 6.0;

چنین متغییر هایی بصورت const هستند و باید مقدار دهی اولیه داشته باشند که آن هم باید یک عبارت ثابت باشه .

برای اینکه بشه مقدار های عبارت های ثابت رو از User-Defined types (نوع های تعریف شده خود کاربر) ساخت ، سازنده ها هم میتونند با واژه ی کلیدیه constexpr اعلان شوند .
بدنه ی یک سازنده ای (Constructor) که با ‌constexpr اعلان شده تنها میتونه دربرگیرنده اعلان ها و Null-Statement باشه ، ولی نمیتونه متغییر هایی رو اعلان کنه یا نوع هایی رو تعریف کنه .
در تابع سازنده مقدار آرگومان باید جوری باشه که پس از جاگزینی مقدار آرگومان اعضای کلاس را با عبارت های ثابت مقدار دهی اولبه کند . و مخرب های چنین نوع هایی باید خیلی ناچیز باشند .
Copy Constructor (سازنده هایی که همون نوع کلاس رو میگیرند و داده های اون رو درون کلاس خودشون کپی میکنند مانند : ( &A::A(A) برای یک کلاسی که Constructor هایی از نوع constexpr دارد بیشتر باید بصورت یک سازنده ی constexpr اعلان شوند . برای اینکه اجازه بدند شی های اون کلاس بصورت By-Value از تابع constexpr برگشت داده بشوند . این اجازه میده که کامپایلر کلاس ها رو Compile-Time کپی کنه و هر کار دیگه ای رو روش انجام بدهد .

اگر یک تابع constexpr یا Constructor صدا بشه با آرگومان هایی که عبارت های ثابت نیستند . اون تابع جوری صدا زده میشه که انگار constexpr نیست . و مقدار پایانی هم constexpr نخواهد بود.

 

پاسخ داده شده فروردین 27, 1393 بوسیله ی Ali Rahbar (امتیاز 4,240)   6 16 46
ویرایش شده اردیبهشت 6, 1393 بوسیله ی BlueBlade
+7 امتیاز

Hash Tables

 

یکی دیگه از کمبود هایی که قبل از c++11 احساس میشد نبود  ساختار Hash table بود

توی C++11 برای استفاده از hash table میتونید از 4 تا کلاس زیر استفاده کنید :

 

  • std::unordered_set
  • std::unordered_multiset
  • std::unordered_map
  • std::unordered_multimap

 

فرق map با set اینه که توی map ما یک کلید داریم + یک عضو متناظر ولی توی set فقط یک داده داریم

تفاوت multiset , set هم اینه که توی set ما نمی تونیم عنصر تکراری داشته باشیم ولی توی multiset میشه

برای map هم به همین شکل داخل multimap میشه چند تا کلید تکراری هم داشت ولی توی map نمیشه .

 

rvalue refrence,

move semantic,perfect forwarding

 

نوع جدیدی از refrence که در c++11 معرفی شده

با استفاده از rvalue refrence میشه متغیر هایی که بصورت موقت هستند و فقط سمت راست تساوی قرار بگیرند رو تشخیص داد.

مثلا داخل مثال زیر

int a=3;
int b=6;
int c=a*b;

a , b ,c هر 3 تا lvalue هستن

3و6 و a*b هم rvalue چون مقادیری که بر می گردونن موقت هستن و فقط هم سمت راست تساوی میتونن قرار بگیرن !

توضیحات بیشتر رو میتونید در این لینک ببینید :  rvalue , lvalue

از rvalue دو جا داخل c++11 استفاده شده  :

1_ پیاده سازی move semantic

2_ perfect forwarding

که درباره این 2 مورد داخل این لینک توضیح داده شده :منظور از move-semantics در c++11 چیه ؟

 

Variadic templates

قبل از c++11 تعداد پرامتر هایی که میتونستین به template بفرستید باید تعدادشون مشخص باشه 

توی c++11 ّ میشه هر تعداد پارامتر که لازم داشت رو فرستاد به template

برای تعریف به این شکل عمل میشه :

template<typename... arguments> 
class MyClass;

مثلا فرض کنید میخواهید یک تابع برای اضافه کردن تعداد نامشخص  عنصر بصورت همزمان به لیستی که نوشتید داشته باشید(برای صرف نظر کردن از جزییات با آرایه نوشته شده)

#include <iostream>

class MyList
{
public:
    MyList():pos(0){}

    template <typename T,typename ...Types>
    void push_back(const T& val,const Types& ... args)
    {
        data[pos]=val;
        pos++;
        push_back(args ... );
    }

    void print()
    {
        for(int i=0;i<pos;i++)
            std::cout<<data[i]<<" ";
    }

private:
    void push_back(){}//end
    int pos;
    int data[100];
};

int main()
{
    MyList list;
    list.push_back(1);//ok
    list.push_back(1,2,3,4);//ok
    list.push_back(1,2,3,4,4,32,234,4,2);//ok
    //...
    list.print();

}

دقت کنید که کد بالا به شکل بازگشتی اجرا میشه هر بار که push_back اجرا میشه اولین متغیر میره داخل T و بقیه متغیر ها دوباره با arg... فرستاده میشن به تابع نهایتا هم که متغیری باقی نموند push_back بدون ورودی اجرا میشه و تموم میشه .

مثال 2 :  فرض کنید نیاز به یک تابع دارید که تعداد نامعلومی از string می گیره و یک وکتور از اونا بر میگردونه میتونید به ای شکل عمل کنید

#include <iostream>
#include <vector>
#include <string>

void make(std::vector<std::string>&){}//end

template <typename T,typename ...Types>
void make(std::vector<std::string>& result,const T& val,const Types& ... args)
{
    result.push_back(val);
    make(result,args ... );
}

template <typename ...Types>
std::vector<std::string> make(const Types& ... args)
{
    std::vector<std::string> result;
    make(result,args ... );
    return result;
}


int main()
{
    std::vector<std::string> result=make("str1","str2");

    for(auto i:result)
    {
        std::cout<<i<<std::endl;
    }
}

داخل کد بالا اول overload سوم اجرا میشه بعد تابع وسطی چند بار صدا زده میشه و در نهایت هم تابع make بدون ورودی .

(نکته : کد بالا داخل visual studio 2012 به خاطر پشتیبانی کردن نصفه نیمه از c++11 اجرا نمیشه ولی از visual studio 2013 به بعد مشکلی نداره )

پاسخ داده شده اردیبهشت 6, 1393 بوسیله ی BlueBlade (امتیاز 15,315)   15 18 89
ویرایش شده اردیبهشت 7, 1393 بوسیله ی BlueBlade
خودتون گفته بودین به جای variadic templates میشد در c++ قدیمی از va_list استفاده کرد.
بی زحمت ویژگی های C++14 ر. هم بگین که با GCC 4.9 اومده.
va_list میشه استفاده کرد ولی محدودیت داره به va_list نمیشه ساختار غیر POD فرستاد .
+2 امتیاز

(Non static data member initialize (c++11

از C++11 به بعد میشه به جای کد زیر :

class A {
public:
	A() : a(7) {}
private:
	int a;
};

که داخل سازنده پیش فرض به متغیر a عضو کلاس مقدار 7 میده به این شکل عمل کرد :

class A {
private:
	int a = 7;//ghabl az c++11 error dade mishod
};

(generic lambda (c++14

از C++14 به بعد ورودی lambda ها میتونه auto باشه .
مثال :
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>

int main(){
     std::vector<int> vec={1,2,6,7,8};
     std::vector<std::string> vec2={"7","khat","code"};
     
     //ghabl az c++14 mojaz be estefade az [](auto a,auto b) naboodid 
     auto gLambda=[](auto a,auto b){ return a>b;};
     
     std::sort(vec.begin(),vec.end(),gLambda);
     for(auto i:vec)
       std::cout<<i<<" ";
     std::cout<<'\n';
     
     std::sort(vec2.begin(),vec2.end(),gLambda);
     for(auto i:vec2)
       std::cout<<i<<" ";
     
}

اجرای کد

(Binary literals (c++14

از C++14 به بعد میتونید برای تعریف اعداد باینری از پیشوند 0b استفاده کنید .

مثال :

#include <iostream>
#include <bitset>

int main(){
     auto binary =0b0110111;
     auto binary2=0b0101100;
     auto sum=binary+binary2;
     std::cout<<std::bitset<7>(binary)<<'\n'; 
     std::cout<<std::bitset<7>(binary2)<<'\n'; 
     std::cout<<std::bitset<7>(sum); 
}
/*khorooji: 
0110111
0101100
1100011
*/

اجرای کد

(Return type deduction (c++14

خروجی تابع از c++14 به بعد میتونه auto باشه و بر اساس مقداری که return می کنید نوع تابع تشخیص داده میشه .

مثال :

#include <iostream>
#include <string>
#include <vector>

//2 voroodi a,b ra baham tarikib va return mikonad
template<class T>
auto concatenate(T a,T b){
    auto res=a;
    for(const auto& item:b){
        res.push_back(item);
    }
    return res;
}
int main(){
     std::string a="7khat ";
     std::string b="code";
     std::cout<<concatenate(a,b)<<'\n';
     
     std::vector<int> vec1={1,2};
     std::vector<int> vec2={3,4};
     auto res= concatenate(vec1,vec2);
     
     for(auto item:res) 
       std::cout<<item<<" ";
}

/* khorooji:
7khat code
1 2 3 4 
*/

 

(Initialized lambda captures (c++14
از c++14 به بعد میشه مقداری که داخل lambda ما capture می کنیم رو مقدار اولیه هم بدیم .
مثال :
#include <iostream>
#include <string>
#include <vector>

int main(){
     int x;
     //x capture mishe va meghdare avalie 5 migire
     auto test=[x=5](){
         std::cout<<x;
     };
     test();
}

//khorooji: 5

(Single quotation mark as seprator (c++14

از c++14 به بعدبرای جدا کردن رقم های یک عدد بزرگ میشه از ' استفاده کرد :

int a=100'542'123;

 

 

پاسخ داده شده بهمن 16, 1393 بوسیله ی BlueBlade (امتیاز 15,315)   15 18 89
ویرایش شده بهمن 16, 1393 بوسیله ی BlueBlade
...