AMcoder - javascript, python, java, html, php, sql

Ժամանակավոր օբյեկտների կյանքի ժամկետը ցուցակի սկզբնավորման ժամանակ

Ես միշտ ենթադրում էի, որ ժամանակավոր օբյեկտները ապրում են մինչև ամբողջական արտահայտման ավարտը: Այնուամենայնիվ, այստեղ կա հետաքրքիր տարբերություն std::vector-ի և զանգվածի սկզբնավորումների միջև:

Խնդրում ենք հաշվի առնել հետևյալ կոդը.

#include <iostream>
#include <vector>

struct ID{ 
  static int cnt;
  // the number of living object of class ID at the moment of creation:  
  int id;

  ID():id(++cnt){}

  ~ID(){
     cnt--;
  }
};

int ID::cnt=0;

int main(){

  int arr[]{ID().id, ID().id};
  std::vector<int> vec{ID().id, ID().id};

  std::cout<<" Array: "<<arr[0]<<", "<<arr[1]<<"\n";
  std::cout<<" Vector: "<<vec[0]<<", "<<vec[1]<<"\n";
}

Այս ծրագրի արդյունքը մի փոքր (գոնե ինձ համար) անսպասելի է.

 Array: 1, 1
 Vector: 1, 2

Դա նշանակում է, որ ժամանակավոր օբյեկտները կենդանի են std::vector-ի սկզբնավորման ողջ ընթացքում, բայց զանգվածի դեպքում դրանք ստեղծվում և ոչնչացվում են մեկը մյուսի հետևից: Ես կակնկալեի, որ ժամանակավորները կապրեն մինչև int arr[]{ID().id, ID().id}; ամբողջական արտահայտությունը ավարտվի:

Ստանդարտը նշում է մեկ բացառություն՝ կապված ժամանակավոր օբյեկտների կյանքի տևողության և զանգվածների սկզբնավորման հետ (12.2): Սակայն ես չեմ հասկանում դրա իմաստը և չգիտեմ, թե ինչու է այն կիրառվում տվյալ դեպքում.

Գոյություն ունեն երկու համատեքստ, որոնցում ժամանակավորները ոչնչացվում են լրիվ արտահայտման ավարտից տարբեր կետում: Առաջին համատեքստն այն է, երբ լռելյայն կոնստրուկտորը կանչվում է զանգվածի տարրը սկզբնավորելու համար: Եթե ​​կոնստրուկտորն ունի մեկ կամ մի քանի լռելյայն արգումենտ, ապա լռելյայն արգումենտում ստեղծված յուրաքանչյուր ժամանակավոր արգումենտի ոչնչացումը հաջորդականացվում է զանգվածի հաջորդ տարրի կառուցումից առաջ, եթե այդպիսիք կան:


Արդյունքների ակնարկ տարբեր կոմպիլյատորների միջոցով (MSVS արդյունքը NathanOliver-ի ակնարկն է).

             Array    Vector
clang 3.8    1, 2      1, 2
g++ 6.1      1, 1      1, 2
icpc 16      1, 1      1, 2
MSVS 2015    1, 1      1, 2

Ինչպես նշեց Էկատմուրը, ագրեգատային սկզբնավորման համար braced-init-list-ի յուրաքանչյուր տարր ամբողջական արտահայտություն է, հետևաբար հետևյալ կոդը.

  struct S{
      int a;
      int b;
  } s{ID().id, ID().id};
  std::cout<<" Struct: "<<s.a<<", "<<s.b<<"\n";

պետք է տպել Struct 1, 1 վահանակի վրա: Դա հենց այն է, ինչ անում է g++-ի կազմած ծրագիրը։ Այնուամենայնիվ, clang-ը կարծես սխալ ունի. արդյունքում ստացված ծրագիրը տպում է Struct 1, 2:


Հաղորդվել է, որ սխալ է հնչել՝ https://llvm.org/bugs/show_bug.cgi?id=29080

18.08.2016

  • arr-ն օգտագործում է համախառն սկզբնականացում, իսկ vec-ն օգտագործում է կոնստրուկտորի կանչ: 18.08.2016
  • Թվում է, թե gcc-ի հետ կապված սխալ է, clang-ը տալիս է 1, 2 երկու Դեմոյի համար: 18.08.2016
  • Ամբողջական արտահայտությունն ավելի շուտ ID().id է այստեղ, afair: Այնուամենայնիվ, վեկտորի դեպքում ամենաարտաքին արտահայտությունը կոնստրուկտորի կանչն է սկզբնավորիչ ցուցակի կառուցողին: Ի՞նչ է տեղի ունենում auto &&arr2 = decltype(arr){ID().id, ID().id};-ի համար: 18.08.2016
  • FWIW MSVS 2015 թարմացումը 3 տալիս է նույն արդյունքները, ինչ g++-ը: 18.08.2016
  • @JohannesSchaub-litb auto &&arr2 = decltype(arr){ID().id, ID().id};-ը ստանում է {1,1}, եթե կազմվում է g++-ով 18.08.2016

Պատասխանները:


1

Սա հիմնական թողարկումն է 1343 «Ոչ դասի սկզբնավորման հաջորդականությունը» «, որն ընդունվել է որպես թերության հաշվետվություն 2016 թվականի նոյեմբերին P0570R0: Առաջարկվող բանաձեւը C++17-ի մի մասն է, բայց, հետևաբար, C++14-ի մաս չէ, ուստի (եթե կոմիտեն որոշի C++14-ի ուղղում հրապարակել) սա տարբերության կետ է C++17-ի և C+-ի միջև: +14.

C++14

C++14 ստանդարտի կանոնների համաձայն ճիշտ ելքը զանգվածի համար 1, 1 է, իսկ վեկտորի համար՝ 1, 2; սա պայմանավորված է նրանով, որ վեկտոր կառուցելը (ներառյալ braced-init-list-ից) պահանջում է կանչել կոնստրուկտորին, մինչդեռ զանգված կառուցելը` ոչ:

Լեզուն, որը կարգավորում է սա, գտնվում է [intro.execution]-ում:

10 - լրիվ արտահայտությունը արտահայտություն է, որը այլ արտահայտության ենթաարտահայտում չէ: [...] Եթե լեզվական կառուցվածքը սահմանվում է ֆունկցիայի անուղղակի կանչ առաջացնելու համար, ապա լեզվի կառուցվածքի օգտագործումը համարվում է արտահայտություն այս սահմանման նպատակների համար: [...]

Սա լավ է որպես վերին մակարդակի ակնարկ, բայց այն անպատասխան է թողնում որոշ հարցեր.

  • Հստակ որ լեզվի կոնստրուկցիան հաշվվում է որպես ֆունկցիայի անուղղակի կանչ արտադրող կառուցվածք.
  • Ինչն իրականում համարվում է որպես ֆունկցիայի անուղղակի կանչ. ենթադրաբար օգտագործողի կողմից սահմանված կոնստրուկտորին զանգը ֆունկցիայի կանչ է, բայց ի՞նչ կարելի է ասել այն կոնստրուկտորի մասին, որը լռելյայն է կամ սահմանված է որպես լռելյայն:

Զանգվածը ագրեգատ է, ուստի սկզբնավորվում է braced-init-list-ից՝ համաձայն [dcl.init.aggr]; սա ասում է, որ յուրաքանչյուր տարր սկզբնավորվում է անմիջապես ցանկի համապատասխան տարրից, հետևաբար չկա ֆունկցիայի անուղղակի կանչ (համենայն դեպս, չի համապատասխանում ընդհանուր սկզբնավորմանը): Շարահյուսական մակարդակում, initializer ([dcl.init]/1) ներսում՝ օգտագործելով braced-init-list որպես brace-or-equal-initializerլրիվ արտահայտություններն այն արտահայտություններն են, որոնք պարունակվում են փակագծերում և բաժանվում են ստորակետերով: Յուրաքանչյուր ամբողջական արտահայտման վերջում ժամանակավորների դեստրուկտորներից պահանջվում է գործարկել, քանի որ [class.temporary]-ում նշված երեք համատեքստերից ոչ մեկն այստեղ այդպես չէ:

Վեկտորի սկզբնավորման դեպքը տարբեր է, քանի որ դուք օգտագործում եք initializer_list կոնստրուկտորը, ուստի տեղի է ունենում ֆունկցիայի (այսինքն՝ initializer_list կոնստրուկտորի) անուղղակի կանչը. սա նշանակում է, որ ամբողջ սկզբնավորումը շրջապատող անուղղակի ամբողջական արտահայտություն կա, ուստի ժամանակավորները ոչնչացվում են միայն այն ժամանակ, երբ ավարտվում է վեկտորի սկզբնավորումը:

Շփոթեցնող է, [dcl.init.list] ասում է, որ ձեր կոդը «մոտավորապես համարժեք» է.

const int __a[2] = {int{ID().id}, int{ID().id}};  // #1
std::vector<int> vec(std::initializer_list<int>(__a, __a + 2));

Այնուամենայնիվ, սա պետք է կարդալ համատեքստում. օրինակ, initializer_list-ին աջակցող զանգվածն ունի վեկտորի սկզբնավորմամբ սահմանափակված կյանքի տևողությունը:

Սա շատ ավելի պարզ էր C++03-ում, որն ուներ [intro.execution]-ում.

13 - [Նշում․ . Օրինակ, 8.5-ում initializer-ի մեկ շարահյուսությունը ( expression-list ) է, բայց ստացված կառուցումը ֆունկցիայի կանչ է կառուցողի ֆունկցիայի վրա, որտեղ expression-list որպես փաստարկների ցանկ; նման ֆունկցիայի կանչը լրիվ արտահայտություն է: Օրինակ, 8.5-ում, initializer-ի մեկ այլ շարահյուսություն = initializer-clause է, բայց նորից ստացված կառուցվածքը կարող է լինել ֆունկցիայի կանչ կառուցողի ֆունկցիայի վրա մեկ assignment-expression որպես արգումենտ; կրկին, ֆունկցիայի կանչը լրիվ արտահայտություն է: ]

Այս պարբերությունն ամբողջությամբ ջնջված է C++11-ից. սա ըստ CWG 392 որոշման էր . Արդյունքում առաջացած խառնաշփոթը ենթադրաբար նախատեսված չէր:

C++17

P0570R0-ից հետո [intro.execution] նշում է, որ լրիվ արտահայտությունը հետևյալն է.

  • an init-declarator ([dcl.decl]) [...] ներառյալ սկզբնավորիչի բաղկացուցիչ արտահայտությունները, կամ [...]
  • արտահայտություն, որը այլ արտահայտության ենթաարտահայտություն չէ և այլ կերպ լրիվ արտահայտման մաս չէ:

Այսպիսով, C++17-ում ամբողջական արտահայտությունը համապատասխանաբար arr[]{ID().id, ID().id} և vec{ID().id, ID().id} է, իսկ ճիշտ ելքը՝ 1, 2 յուրաքանչյուր դեպքում, քանի որ առաջին ժամանակավոր ID-ի ոչնչացումը հետաձգվում է մինչև լրիվ արտահայտման վերջը:

18.08.2016
  • @NathanOliver այո: 18.08.2016
  • Հենց նոր փորձարկվեց MSVS-ը և այն իրեն պահում է g++-ի նման: Մի փոքր մտահոգիչ է, որ բոլոր երեք խոշոր վաճառողները դա սխալ են արել: 18.08.2016
  • Իմ DR-ը վերաբերում էր միայն այն դեպքերին, ինչպիսիք են int a = 5; int b = a++. Համապատասխան SO հարց կա, որ ես այդ ժամանակ արել եմ. Այնուամենայնիվ, ես չեմ կատարել DR փակագծային սկզբնական ցուցակների վերաբերյալ և չունեմ կարծիք, թե ինչ կանոններ են նախատեսված այս հարցի օրինակի վերաբերյալ: Առնվազն ոչ առանց 90-ականների նախագծերի և ամենավերջին զարգացումների հարցում ճաշելու :) 18.08.2016
  • Թվում է նաև, որ այս հարցի վերաբերյալ դեռևս անպատասխան հարց կա stackoverflow.com/questions/6315670/ 18.08.2016
  • Ահա այն հարցը, որ DR-ն հիմնված էր stackoverflow.com/questions/5760866/ 18.08.2016
  • Շնորհակալություն ձեր հիանալի բացատրությունների համար: Եթե ​​ճիշտ հասկացա, դուք ակնարկում եք, որ երկու նախաձեռնություններն էլ պետք է նույն արդյունքը տան։ Ես դժվարանում եմ հավատալ, որ g++-ը, msvs-ը և intel-ը սխալ են ստացվել (տես իմ թարմացումը): Նաև կլանգը չի հետևում ստանդարտին, ինչպես որ կա: Ես այստեղ մի փոքր կորցնում եմ... 19.08.2016
  • @ead Կարծես հիշում եմ, որ վերջերս ինչ-որ տեղ այս մասին խոսակցություններ եղան կլենգ-սմիթի կողմից, այնպես որ, գուցե կլանգերի պահվածքը միտումնավոր է: 19.08.2016
  • Խնդրում եմ, կարո՞ղ եք ցույց տալ դեպքեր, երբ գործառույթ տերմինը նախատեսված չէ կիրառել նաև կոնստրուկտորների համար: Դա ինձ համար նորություն կլիներ 19.08.2016
  • @JohannesSchaub-litb [expr.const] մեկի համար: 20.08.2016
  • @ecatmur նկատի ունեք constexpr ֆունկցիա և constexpr կառուցող տերմինները: Constexpr ֆունկցիան այն ֆունկցիա չէ, որը constexpr է, բայց constexpr ֆունկցիան սահմանված ոչ բաղադրյալ տերմին է. Constexpr սպեցիֆիկատորը, որն օգտագործվում է կոնստրուկտոր չհանդիսացող ֆունկցիայի հայտարարագրման մեջ, հայտարարում է այդ ֆունկցիան որպես constexpr ֆունկցիա. Այսպիսով, երբ expr.const-ն ասում է constexpr ֆունկցիան, այն չի ասում գործառույթ, որը constexpr է, այլ իրականում վերաբերում է constexpr ֆունկցիայի այդ սահմանմանը: 20.08.2016
  • Նկատի ունեցեք, որ expr.const-ն ինքը որպես երկրորդ պարբերակ ունի constexpr կոնստրուկտորից տարբեր ֆունկցիայի կանչ, որը ցույց է տալիս, որ կոնստրուկտորներն այդ պարբերությունում համարվում են գործառույթներ: 20.08.2016
  • @JohannesSchaub-litb Լավ, ես համոզված եմ, շնորհակալություն բացատրելու համար ժամանակ հատկացնելու համար: Ես համապատասխանաբար կվերանայեմ իմ պատասխանը: 22.08.2016
  • @ecatmur Որտեղ ճշգրիտ ստանդարտում ասվում է, որ սկզբնավորիչում ([dcl.init]/1) օգտագործելով braced-init-list-ը որպես brace-or-equal-initializer, լրիվ արտահայտությունները փակագծերում պարունակվող արտահայտություններն են: և բաժանվել ստորակետերով? Ես գտա սա՝ eel.is/c++draft/dcl.init# list-4, բայց այն բացահայտորեն չի օգտագործում ամբողջական արտահայտություն: 08.08.2017
  • @ecatmur Արդյո՞ք դա այն պատճառով է, որ դրանք արտահայտություններ են, և դրանք որևէ այլ արտահայտության մաս չեն, ոչ էլ որևէ այլ ամբողջական արտահայտություն: Եվ երբ braced-list պարունակող կոնստրուկցիան անուղղակիորեն կանչում է ֆունկցիա (վեկտորային կոնստրուկտորի դեպքում)՝ դառնալով ամբողջական արտահայտություն, հիմա ստորակետներով առանձնացված արտահայտությունները լրիվ արտահայտման մաս են։ 08.08.2017
  • @user42768 այո, ճիշտ է: Կարծես թե սա կարող է վերջերս փոխվել eel.is/c++draft/intro .execution#12 այնպես, որ init-declarator-ը լինի ամբողջական արտահայտություն. ես կուսումնասիրեմ փոփոխությունը և կթարմացնեմ իմ պատասխանը: 08.08.2017
  • @ecatmur Այս հարցն ինձ դրդեց տալ հետևյալը. stackoverflow.com/questions/45576309/: Առաջին կետի պատասխանը տրվեց մեկնաբանություններում, իսկ վերջին երկու կետերի համար ես հարցրի այստեղ՝ groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/: Ըստ երևույթին, ստանդարտում ինչ-որ վատ ձևակերպում կա, և init-declarator-ը լրիվ արտահայտություն չէ, այլ դրա կողմից կատարված սկզբնավորումն է (երկրորդ հղում): Սակայն երկրորդ հղումում (իմ պատասխանում) նկարագրված մեկ այլ հակասություն էլ գտա. Խնդրում եմ, կարո՞ղ եք ասել ինձ, արդյոք ես ճիշտ եմ: 11.08.2017
  • @user42768 Եթե Ստանդարտն ասում է, որ init-declarator-ը լրիվ արտահայտություն է, ապա դա հենց դա է. Օրինակի վերաբերյալ մեկնաբանությունը սխալ է կամ ապակողմնորոշող: Շփոթմունքը կարող է առաջանալ այն փաստից, որ այդ դեպքում միակ գործողությունը, որը կատարում է ամբողջական սկզբնավորիչը, կանչն է S::S(int)ին: Init-հայտարարիչը կոնստրուկտ չէ, որը սահմանված է ֆունկցիայի անուղղակի կանչ ստեղծելու համար. այդպիսի օրինակ կլինի if հայտարարությունը, որը սահմանվում է անուղղակիորեն փոխակերպելու իր condition-ի (արտահայտության) արդյունքը bool-ի: 11.08.2017
  • @ecatmur Շնորհակալություն արձագանքի համար։ Այսպիսով, վերջապես, եզրակացությունն այն է, որ մեկնաբանությունը սխալ է: Այնուամենայնիվ, eel.is/c++draft/class: base.init#7.note-1 ասում է, որ յուրաքանչյուր mem-initializer-ի կողմից կատարված նախաձեռնումը կազմում է ամբողջական արտահայտություն [...].. Ես փորձեցի հետևել կատարված փոփոխություններին ամբողջական արտահայտությունները սահմանող պարբերությանը և ես գտա սա՝ open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#392: Հեռացված տեքստը հուշում է (գոնե ինձ), որ init-հայտարարիչը արտադրում է անուղղակի ֆունկցիայի կանչ: 11.08.2017
  • @user42768 Կրկին, նշումը ոչ նորմատիվ է. ստանդարտի տեքստը գերակա է: Init-հայտարարիչն արտադրում է իմպլիցիտ կանչ ֆունկցիային միայն այն դեպքում, եթե դասը ունի օգտագործողի կողմից սահմանված կոնստրուկտոր, որի դեպքում կոնստրուկտոր ֆունկցիայի կանչը համարվում է արտահայտություն, որը հանդիսանում է ամբողջական արտահայտման մաս, որը հանդիսանում է init-ը: հայտարարատու. Եթե ​​դասը ագրեգատ է, ֆունկցիայի անուղղակի կանչ չի լինի, բայց init-հայտարարիչը դեռ լրիվ արտահայտություն է: 11.08.2017
  • Հիմա կարծես թե ամեն ինչ պարզ է. Եզրակացնենք, որ ստանդարտում այդ մեկնաբանությունը սխալ է: Շատ շնորհակալ եմ բացատրելու համար ժամանակ տրամադրելու համար: 11.08.2017
  • Նոր նյութեր

    Օգտագործելով Fetch Vs Axios.Js-ը՝ HTTP հարցումներ կատարելու համար
    JavaScript-ը կարող է ցանցային հարցումներ ուղարկել սերվեր և բեռնել նոր տեղեկատվություն, երբ դա անհրաժեշտ լինի: Օրինակ, մենք կարող ենք օգտագործել ցանցային հարցումը պատվեր ներկայացնելու,..

    Տիրապետել հանգստության արվեստին. մշակողի ուղեցույց՝ ճնշման տակ ծաղկելու համար
    Տիրապետել հանգստության արվեստին. մշակողի ուղեցույց՝ ճնշման տակ ծաղկելու համար Ինչպե՞ս հանգստացնել ձեր միտքը և աշխատեցնել ձեր պրոցեսորը: Ինչպես մնալ հանգիստ և զարգանալ ճնշման տակ...

    Մեքենայի ուսուցում բանկային և ֆինանսների ոլորտում
    Բարդ, խելացի անվտանգության համակարգերը և հաճախորդների սպասարկման պարզեցված ծառայությունները բիզնեսի հաջողության բանալին են: Ֆինանսական հաստատությունները, մասնավորապես, պետք է առաջ մնան կորի..

    Ես AI-ին հարցրի կյանքի իմաստը, այն ինչ ասում էր, ցնցող էր:
    Այն պահից ի վեր, երբ ես իմացա Արհեստական ​​ինտելեկտի մասին, ես հիացած էի այն բանով, թե ինչպես է այն կարողանում հասկանալ մարդկային նորմալ տեքստը, և այն կարող է առաջացնել իր սեփական արձագանքը դրա..

    Ինչպես սովորել կոդավորումը Python-ում վագրի պես:
    Սովորելու համար ծրագրավորման նոր լեզու ընտրելը բարդ է: Անկախ նրանից, թե դուք սկսնակ եք, թե առաջադեմ, դա օգնում է իմանալ, թե ինչ թեմաներ պետք է սովորել: Ծրագրավորման լեզվի հիմունքները, դրա..

    C++-ի օրական բիթ(ե) | Ամենաերկար պալինդրոմային ենթաշարը
    C++ #198-ի ամենօրյա բիթ(ե), Ընդհանուր հարցազրույցի խնդիր. Ամենաերկար պալինդրոմային ենթատող: Այսօր մենք կանդրադառնանք հարցազրույցի ընդհանուր խնդրին. Ամենաերկար palindromic substring...

    Kydavra ICAReducer՝ ձեր տվյալների ծավալայինությունը նվազեցնելու համար
    Ի՞նչ է ICAReducer-ը: ICAReducer-ն աշխատում է հետևյալ կերպ. այն նվազեցնում է նրանց միջև բարձր փոխկապակցված հատկանիշները մինչև մեկ սյունակ: Բավականին նման է PCAreducer-ին, չնայած այն..


    © 2024 amcode.ru, AMcoder - javascript, python, java, html, php, sql