ความพยายามในการกำหนดค่าเริ่มต้นของฉันถูกตีความเป็นการประกาศฟังก์ชั่นและเหตุใดจึงไม่ A (()); แก้มันได้หรือไม่


158

ในบรรดาหลาย ๆ สิ่ง Stack Stack ล้นได้สอนฉันคือสิ่งที่เรียกว่า "แจงที่สุด vexing" ซึ่งแสดงให้เห็นคลาสสิกกับสายเช่น

A a(B()); //declares a function

ในขณะนี้สำหรับส่วนใหญ่โดยสัญชาตญาณดูเหมือนจะเป็นการประกาศของวัตถุaประเภทAรับBวัตถุชั่วคราวเป็นพารามิเตอร์ตัวสร้างมันจริง ๆ แล้วการประกาศฟังก์ชั่นaกลับมาการAใช้ตัวชี้ไปยังฟังก์ชั่นที่กลับมาBและตัวเองไม่ใช้พารามิเตอร์ . ในทำนองเดียวกันบรรทัด

A a(); //declares a function

ยังอยู่ภายใต้หมวดหมู่เดียวกันเนื่องจากแทนที่จะเป็นวัตถุมันประกาศฟังก์ชั่น ตอนนี้ในกรณีแรกวิธีแก้ปัญหาตามปกติสำหรับปัญหานี้คือการเพิ่มชุดพิเศษของวงเล็บ / วงเล็บรอบในB()ขณะที่คอมไพเลอร์จะตีความมันเป็นการประกาศของวัตถุ

A a((B())); //declares an object

อย่างไรก็ตามในกรณีที่สองการทำเช่นเดียวกันทำให้เกิดข้อผิดพลาดในการคอมไพล์

A a(()); //compile error

คำถามของฉันคือทำไม ใช่ฉันทราบดีว่า 'วิธีแก้ไข' ที่ถูกต้องคือเปลี่ยนเป็นA a;แต่ฉันอยากรู้ว่าอะไรคือสิ่งที่พิเศษ()สำหรับคอมไพเลอร์ในตัวอย่างแรกซึ่งไม่ทำงานเมื่อนำมาใช้ใหม่ ตัวอย่างที่สอง เป็นA a((B()));วิธีแก้ปัญหาข้อยกเว้นเฉพาะเขียนลงในมาตรฐานหรือไม่


20
(B())เป็นเพียงการแสดงออก C ++ ไม่มีอะไรเพิ่มเติม มันไม่มีข้อยกเว้นใด ๆ ความแตกต่างเพียงอย่างเดียวที่ทำให้เป็นคือไม่มีวิธีที่จะแยกวิเคราะห์เป็นประเภทและดังนั้นจึงไม่
Pavel Minaev

12
มันก็ควรจะตั้งข้อสังเกตว่ากรณีที่สองA a();คือไม่ได้ของประเภทเดียวกัน สำหรับคอมไพเลอร์ไม่มีวิธีที่แตกต่างกันในการแยกวิเคราะห์: ตัวเริ่มต้นในสถานที่นั้นไม่เคยประกอบด้วยวงเล็บเปล่าดังนั้นนี่เป็นการประกาศฟังก์ชันเสมอ
Johannes Schaub - litb

11
จุดที่ยอดเยี่ยมของ litb นั้นเป็นสิ่งที่สำคัญ แต่ก็สำคัญและคุ้มค่าที่จะเน้นย้ำ - เหตุผลที่ความกำกวมมีอยู่ในคำประกาศนี้ 'A (B ())' อยู่ในการแยกวิเคราะห์ 'B ()' -> มันสามารถเป็นได้ทั้งการแสดงออก & การประกาศ & คอมไพเลอร์จะต้อง 'เลือก' เดวิฟกว่า expr - ดังนั้นถ้า B () เป็นเดซิเบลดังนั้น 'a' จะสามารถเป็น func เดอร์ (ไม่ใช่ตัวแปรเดค) หาก '()' ได้รับอนุญาตให้เป็นตัวเริ่มต้น 'A ()' จะไม่ชัดเจน - แต่ไม่ใช่ expr vs den แต่ แต่ var decl vs func den - ไม่มีกฎใดที่จะต้องการหนึ่ง decl มากกว่าอีก - และ '() 'ไม่ได้รับอนุญาตเป็นเครื่องมือเริ่มต้นที่นี่ - และความกำกวมไม่เพิ่มขึ้น
Faisal Vali

6
A a();คือไม่ได้ตัวอย่างของการที่แยกรบกวนมากที่สุด มันเป็นเพียงการประกาศฟังก์ชั่นเช่นเดียวกับใน C
Pete Becker

2
"การแก้ไข 'ที่ถูกต้องคือเปลี่ยนเป็นA a;" ผิด ที่จะไม่ให้คุณเริ่มต้นประเภท POD เพื่อให้ได้การเขียนที่A a{};ซับซ้อน
ไชโยและ hth - Alf

คำตอบ:


70

ไม่มีคำตอบที่รู้แจ้งเป็นเพียงเพราะมันไม่ได้ถูกกำหนดเป็นไวยากรณ์ที่ถูกต้องโดยภาษา C ++ ... ดังนั้นจึงเป็นเช่นนั้นตามคำจำกัดความของภาษา

หากคุณมีนิพจน์ภายในแสดงว่าถูกต้อง ตัวอย่างเช่น:

 ((0));//compiles

ง่ายยิ่งขึ้น: เพราะ(x)เป็นนิพจน์ C ++ ที่ถูกต้องขณะที่()ไม่ใช่

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับวิธีการกำหนดภาษาและวิธีการทำงานของคอมไพเลอร์คุณควรเรียนรู้เกี่ยวกับทฤษฎีภาษาอย่างเป็นทางการหรือเพิ่มเติมโดยเฉพาะบริบทฟรีไวยากรณ์ (CFG)และวัสดุที่เกี่ยวข้องเช่นเครื่องจักรสถานะ จำกัด หากคุณสนใจว่าแม้ว่าหน้าวิกิพีเดียจะไม่เพียงพอคุณจะต้องได้รับหนังสือ


45
ง่ายยิ่งขึ้น: เพราะ(x)เป็นนิพจน์ C ++ ที่ถูกต้องขณะที่()ไม่ใช่
Pavel Minaev

ฉันได้รับการยอมรับคำตอบนี้ในความคิดเห็นของพาเวลนอกจากนี้คำถามแรกของฉันช่วยฉันออกมาก
GRB


29

C ประกาศฟังก์ชั่น

ก่อนอื่นมี C. ใน C A a()คือการประกาศฟังก์ชัน ตัวอย่างเช่นputcharมีการประกาศดังต่อไปนี้ โดยปกติแล้วการประกาศดังกล่าวจะถูกเก็บไว้ในไฟล์ส่วนหัว แต่ไม่มีอะไรหยุดคุณจากการเขียนด้วยตนเองหากคุณรู้ว่าการประกาศฟังก์ชั่นมีลักษณะอย่างไร ชื่ออาร์กิวเมนต์เป็นทางเลือกในการประกาศดังนั้นฉันจึงละเว้นในตัวอย่างนี้

int putchar(int);

สิ่งนี้อนุญาตให้คุณเขียนโค้ดแบบนี้

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C ยังช่วยให้คุณสามารถกำหนดฟังก์ชั่นที่รับฟังก์ชั่นเป็นอาร์กิวเมนต์โดยมีไวยากรณ์ที่อ่านง่ายซึ่งดูเหมือนว่าจะเป็นการเรียกใช้ฟังก์ชัน (เช่นกันมันสามารถอ่านได้

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

ดังที่ฉันได้กล่าวไว้ C อนุญาตให้ละเว้นชื่ออาร์กิวเมนต์ในไฟล์ส่วนหัวดังนั้นoutput_resultจะมีลักษณะเช่นนี้ในไฟล์ส่วนหัว

int output_result(int());

หนึ่งอาร์กิวเมนต์ในตัวสร้าง

คุณจำไม่ได้เหรอ? ผมขอเตือนคุณ

A a(B());

ใช่มันเป็นฟังก์ชั่นการประกาศเดียวกัน Aเป็นint, aเป็นoutput_resultและเป็นBint

คุณสามารถสังเกตเห็นความขัดแย้งของ C ได้อย่างง่ายดายด้วยคุณสมบัติใหม่ของ C ++ เป็นที่แน่นอนก่อสร้างเป็นชื่อชั้นและวงเล็บและไวยากรณ์ประกาศสลับกับแทน() =จากการออกแบบ C ++ พยายามที่จะเข้ากันได้กับรหัส C และดังนั้นจึงต้องจัดการกับกรณีนี้ - แม้ว่าจะไม่มีใครใส่ใจ ดังนั้นคุณสมบัติ C เก่าจึงมีความสำคัญเหนือกว่าคุณสมบัติ C ++ ใหม่ ไวยากรณ์ของการประกาศพยายามที่จะจับคู่ชื่อเป็นฟังก์ชั่นก่อนที่จะย้อนกลับไปที่ไวยากรณ์ใหม่ด้วย()ถ้ามันล้มเหลว

หากหนึ่งในคุณลักษณะเหล่านั้นไม่มีอยู่หรือมีไวยากรณ์ที่แตกต่างกัน (เช่น{}ใน C ++ 11) ปัญหานี้จะไม่เกิดขึ้นสำหรับไวยากรณ์ด้วยอาร์กิวเมนต์เดียว

ตอนนี้คุณอาจถามว่าทำไมA a((B()))งาน ดีขอประกาศของoutput_resultมีวงเล็บไร้ประโยชน์

int output_result((int()));

มันจะไม่ทำงาน ไวยากรณ์ต้องการตัวแปรที่ไม่อยู่ในวงเล็บ

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

อย่างไรก็ตาม C ++ คาดว่านิพจน์มาตรฐานที่นี่ ใน C ++ คุณสามารถเขียนรหัสต่อไปนี้

int value = int();

และรหัสต่อไปนี้

int value = ((((int()))));

C ++ คาดว่านิพจน์ภายในวงเล็บจะเป็น ... ทั้ง ... นิพจน์ตรงข้ามกับประเภท C ที่คาดไว้ วงเล็บไม่ได้มีความหมายอะไรเลย อย่างไรก็ตามโดยการใส่วงเล็บที่ไม่มีประโยชน์การประกาศฟังก์ชัน C ไม่ตรงกันและไวยากรณ์ใหม่สามารถจับคู่ได้อย่างถูกต้อง (ซึ่งคาดว่าจะแสดงออกเช่น2 + 2)

อาร์กิวเมนต์เพิ่มเติมในตัวสร้าง

แน่นอนหนึ่งอาร์กิวเมนต์เป็นสิ่งที่ดี แต่สิ่งที่เกี่ยวกับสอง? ไม่ใช่ว่าผู้สร้างอาจมีข้อโต้แย้งเพียงข้อเดียว หนึ่งในคลาสที่มีอาร์กิวเมนต์สองตัวคือstd::string

std::string hundred_dots(100, '.');

ทั้งหมดนี้เป็นเรื่องที่ดีและดี (โดยทางเทคนิคแล้วมันจะมีการแยกวิเคราะห์ส่วนใหญ่ถ้ามันจะเขียนเป็นstd::string wat(int(), char())แต่ขอซื่อสัตย์ - ผู้ที่จะเขียนว่า แต่ขอสมมติว่ารหัสนี้มีปัญหาน่ารำคาญคุณจะคิดว่าคุณต้องใส่ ทุกอย่างในวงเล็บ

std::string hundred_dots((100, '.'));

ไม่มาก

<stdin>:2:36: error: invalid conversion from char to const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

ฉันไม่แน่ใจว่าทำไมกรัม ++ พยายามที่จะแปลงไปchar const char *ไม่ว่าจะด้วยวิธีใดตัวสร้างจะถูกเรียกด้วยค่าชนิดcharเดียว ไม่มีโอเวอร์โหลดที่มีอาร์กิวเมนต์ชนิดcharเดียวดังนั้นคอมไพเลอร์จึงสับสน คุณอาจถามว่า - ทำไมอาร์กิวเมนต์เป็นประเภทถ่าน?

(100, '.')

ใช่,นี่คือโอเปอเรเตอร์คอมม่า ตัวดำเนินการเครื่องหมายจุลภาคใช้เวลาสองข้อโต้แย้งและให้อาร์กิวเมนต์ด้านขวา มันไม่มีประโยชน์จริง ๆ แต่เป็นสิ่งที่เป็นที่รู้จักสำหรับคำอธิบายของฉัน

แต่เพื่อแก้ปัญหาการแจงส่วนใหญ่ต้องใช้รหัสต่อไปนี้แทน

std::string hundred_dots((100), ('.'));

อาร์กิวเมนต์อยู่ในวงเล็บไม่ใช่นิพจน์ทั้งหมด ในความเป็นจริงแล้วนิพจน์เพียงข้อเดียวจำเป็นต้องอยู่ในวงเล็บเนื่องจากมันเพียงพอที่จะแยกจากไวยากรณ์ C เล็กน้อยเพื่อใช้คุณลักษณะ C ++ สิ่งต่าง ๆ นำเราไปสู่จุดที่ไม่มีข้อโต้แย้ง

ไม่มีข้อโต้แย้งในตัวสร้าง

คุณอาจสังเกตเห็นeighty_fourฟังก์ชั่นในคำอธิบายของฉัน

int eighty_four();

ใช่สิ่งนี้ได้รับผลกระทบจากการแยกวิเคราะห์ที่รบกวนที่สุด มันเป็นคำจำกัดความที่ถูกต้องและเป็นไปได้ว่าคุณเคยเห็นถ้าคุณสร้างไฟล์ส่วนหัว (และคุณควร) การเพิ่มวงเล็บไม่สามารถแก้ไขได้

int eighty_four(());

เหตุผลที่เป็นเช่นนั้น? ดี()ไม่ได้เป็นการแสดงออก ใน C ++ คุณต้องใส่นิพจน์ระหว่างวงเล็บ คุณไม่สามารถเขียนauto value = ()ด้วยภาษา C ++ ได้เพราะ()ไม่ได้หมายความว่าอะไร (และแม้ว่าจะเป็นเช่นเดียวกับ tuple ที่ว่างเปล่า (ดู Python) ก็จะเป็นหนึ่งอาร์กิวเมนต์ไม่ใช่ศูนย์) ในทางปฏิบัติหมายความว่าคุณไม่สามารถใช้ไวยากรณ์ชวเลขได้โดยไม่ต้องใช้ไวยากรณ์ของ C ++ 11 {}เนื่องจากไม่มีนิพจน์ที่จะใส่ในวงเล็บและไวยากรณ์ C สำหรับการประกาศฟังก์ชันจะใช้เสมอ


12

คุณสามารถแทน

A a(());

ใช้

A a=A();

32
'วิธีแก้ปัญหาที่ดีกว่า' ไม่เทียบเท่า int a = int();เริ่มต้นaด้วย 0 int a;จะaไม่กำหนดค่าเริ่มต้น วิธีแก้ปัญหาที่ถูกต้องคือใช้A a = {};สำหรับการรวมA a;เมื่อการกำหนดค่าเริ่มต้นทำในสิ่งที่คุณต้องการและA a = A();ในกรณีอื่นทั้งหมด - หรือใช้A a = A();อย่างสม่ำเสมอ ใน C ++ 11 เพียงใช้A a {};
Richard Smith

6

parens ที่อยู่ด้านในสุดในตัวอย่างของคุณจะเป็นนิพจน์และใน C ++ ไวยากรณ์จะกำหนดexpressionให้เป็นอย่างassignment-expressionใดอย่างหนึ่งexpressionตามด้วยคอมมาและอีกอันหนึ่งassignment-expression(ภาคผนวก A.4 - สรุปไวยากรณ์ / นิพจน์)

ไวยากรณ์กำหนดเพิ่มเติมว่าassignment-expressionเป็นหนึ่งในหลาย ๆ ประเภทของการแสดงออกไม่มีซึ่งจะไม่มีอะไร (หรือเพียงช่องว่าง)

ดังนั้นเหตุผลที่คุณขาดไม่ได้A a(())ก็เพราะไวยากรณ์ไม่อนุญาต อย่างไรก็ตามฉันไม่สามารถตอบได้ว่าทำไมคนที่สร้าง C ++ ไม่อนุญาตให้ใช้ parens ที่ว่างนี้เป็นกรณีพิเศษบางประเภท - ฉันเดาว่าพวกเขาไม่ควรใส่เคสพิเศษหากมี ทางเลือกที่สมเหตุสมผล

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.