ความถูกต้องตามกฎหมายของ COW std :: การใช้สตริงใน C ++ 11


117

เป็นความเข้าใจของฉันที่ว่าการคัดลอกเมื่อเขียนไม่ใช่วิธีที่เป็นไปได้std::stringในการนำการปฏิบัติตามC ++ 11 มาใช้ แต่เมื่อมีการอภิปรายเมื่อเร็ว ๆ นี้ฉันพบว่าตัวเองไม่สามารถสนับสนุนคำพูดนั้นได้โดยตรง

ฉันถูกต้องหรือไม่ที่ C ++ 11 ไม่ยอมรับการใช้งาน COW ของstd::string?

หากเป็นเช่นนั้นข้อ จำกัด นี้มีการระบุไว้อย่างชัดเจนในมาตรฐานใหม่ (ที่ไหน) หรือไม่?

หรือข้อ จำกัด นี้โดยนัยในแง่ที่ว่ามันเป็นผลรวมของความต้องการใหม่ในติ๊ดว่าการดำเนินการตามวัวของstd::string std::stringในกรณีนี้ฉันสนใจในบทและรูปแบบกลอนที่มาของ 'C ++ 11 ห้ามใช้std::stringงานCOW อย่างมีประสิทธิภาพ'


5
ข้อผิดพลาด GCC สตริงวัวของพวกเขาคือgcc.gnu.org/bugzilla/show_bug.cgi?id=21334#c45 หนึ่งในจุดบกพร่องที่ติดตามการใช้งานคอมไพล์ C ++ 11 ใหม่ของ std :: string ใน libstdc ++ คือgcc.gnu.org/bugzilla/show_bug.cgi?id=53221
user7610

คำตอบ:


120

ไม่อนุญาตเนื่องจากตามมาตรฐาน 21.4.1 p6 อนุญาตให้ใช้ตัวทำซ้ำ / การอ้างอิงไม่ถูกต้องสำหรับ

- เป็นอาร์กิวเมนต์ของฟังก์ชันไลบรารีมาตรฐานใด ๆ โดยอ้างถึง non-const basic_string เป็นอาร์กิวเมนต์

- การเรียกใช้ฟังก์ชันที่ไม่ใช่สมาชิก const ยกเว้นตัวดำเนินการ [], ที่, ด้านหน้า, ด้านหลัง, เริ่มต้น, rbegin, สิ้นสุดและการแสดงผล

สำหรับสตริง COW การเรียก non-const operator[]จะต้องมีการทำสำเนา (และการอ้างอิงที่ไม่ถูกต้อง) ซึ่งย่อหน้าข้างบนไม่อนุญาต ดังนั้นจึงไม่ถูกกฎหมายอีกต่อไปที่จะมีสตริง COW ใน C ++ 11


4
เหตุผลบางประการ: N2534
MM

8
-1ตรรกะไม่อุ้มน้ำ ในช่วงเวลาของการคัดลอก COW ไม่มีการอ้างอิงหรือตัวทำซ้ำที่สามารถทำให้เป็นโมฆะได้ประเด็นทั้งหมดของการทำสำเนาคือการได้รับการอ้างอิงหรือตัวทำซ้ำดังกล่าวแล้วดังนั้นการคัดลอกจึงจำเป็น แต่อาจเป็นไปได้ว่า C ++ 11 ไม่อนุญาตการใช้งาน COW
ไชโยและ hth - Alf

11
@ Cheersandhth. -Alf: ตรรกะสามารถมองเห็นได้ในสิ่งต่อไปนี้หากอนุญาต COW: std::string a("something"); char& c1 = a[0]; std::string b(a); char& c2 = a[1]; c1 เป็นการอ้างอิงถึง a. จากนั้นคุณ "คัดลอก" ก. จากนั้นเมื่อคุณพยายามใช้การอ้างอิงเป็นครั้งที่สองจะต้องทำสำเนาเพื่อให้ได้การอ้างอิงที่ไม่ใช่ const เนื่องจากมีสองสตริงที่ชี้ไปที่บัฟเฟอร์เดียวกัน สิ่งนี้จะต้องทำให้การอ้างอิงครั้งแรกที่ใช้เป็นโมฆะและขัดต่อส่วนที่ยกมา
Dave S

9
@ Cheersandhth. -Alf ตามนี้อย่างน้อยการใช้งาน COW ของ GCC ก็ทำตามที่ DaveS พูด ดังนั้นอย่างน้อยรูปแบบของวัวนั้นจึงถูกห้ามโดยมาตรฐาน
Tavian Barnes

4
@Alf: คำตอบนี้ระบุว่า non-const operator[](1) ต้องทำสำเนาและ (2) การทำเช่นนั้นผิดกฎหมาย คุณไม่เห็นด้วยกับสองประเด็นใด เมื่อดูความคิดเห็นแรกของคุณดูเหมือนว่าการใช้งานสามารถแชร์สตริงได้อย่างน้อยก็ภายใต้ข้อกำหนดนี้จนถึงเวลาที่มีการเข้าถึง แต่การเข้าถึงทั้งแบบอ่านและเขียนจะต้องยกเลิกการแชร์ นั่นคือเหตุผลของคุณหรือไม่?
Ben Voigt

48

คำตอบโดยเดฟ Sและ gbjbaanbมีความถูกต้อง (และของ Luc Danton ก็ถูกต้องเช่นกันแม้ว่าจะเป็นผลข้างเคียงของการห้ามสตริง COW มากกว่ากฎเดิมที่ห้ามก็ตาม)

แต่เพื่อล้างความสับสนฉันจะเพิ่มการอธิบายเพิ่มเติม ความคิดเห็นต่างๆเชื่อมโยงไปยังความคิดเห็นของฉันใน Bugzilla GCCซึ่งให้ตัวอย่างต่อไปนี้:

std::string s("str");
const char* p = s.data();
{
    std::string s2(s);
    (void) s[0];
}
std::cout << *p << '\n';  // p is dangling

ประเด็นของตัวอย่างนั้นคือการแสดงให้เห็นว่าเหตุใดสตริงการนับการอ้างอิง (COW) ของ GCC จึงไม่ถูกต้องใน C ++ 11 มาตรฐาน C ++ 11 กำหนดให้รหัสนี้ทำงานได้อย่างถูกต้อง ไม่มีสิ่งใดในรหัสที่อนุญาตpให้ใช้งานไม่ได้ใน C ++ 11

ใช้อ้างอิงนับ GCC เก่าของstd::stringการดำเนินการตามรหัสที่มีพฤติกรรมที่ไม่ได้กำหนดเพราะp เป็นโมฆะกลายเป็นตัวชี้ห้อย (สิ่งที่เกิดขึ้นคือเมื่อs2ถูกสร้างขึ้นจะแชร์ข้อมูลด้วยsแต่การได้รับการอ้างอิงที่ไม่ใช่ const ผ่านs[0]จำเป็นต้องมีการยกเลิกการแชร์ข้อมูลดังนั้นs"copy on write" ก็เช่นกันเนื่องจากการอ้างอิงs[0]อาจถูกใช้เพื่อเขียนลงไปsจากนั้นจึงs2ไป อยู่นอกขอบเขตทำลายอาร์เรย์ที่ชี้โดยp)

มาตรฐาน C ++ 03 อนุญาตให้มีพฤติกรรมนั้นอย่างชัดเจนใน 21.3 [lib.basic.string] p5 โดยระบุว่าหลังจากการเรียกไปยังdata()การเรียกครั้งแรกถึงoperator[]()อาจทำให้ตัวชี้การอ้างอิงและตัวทำซ้ำเป็นโมฆะ ดังนั้นสตริง COW ของ GCC จึงเป็นการใช้งาน C ++ 03 ที่ถูกต้อง

มาตรฐาน C ++ 11 ไม่อนุญาตให้เกิดพฤติกรรมนั้นอีกต่อไปเนื่องจากการไม่เรียกไปยังoperator[]()อาจทำให้ตัวชี้การอ้างอิงหรือตัวทำซ้ำเป็นโมฆะโดยไม่คำนึงว่าพวกเขาจะติดตามการเรียกร้องdata()หรือไม่

ดังนั้นตัวอย่างข้างต้นต้องทำงานใน C ++ 11 แต่ใช้ไม่ได้กับสตริง COW ของ libstdc ++ ดังนั้นสตริง COW ชนิดนั้นจึงไม่ได้รับอนุญาตใน C ++ 11


3
การใช้งานที่ยกเลิกการแชร์ในการเรียกไปยัง.data()(และในทุกการส่งกลับของตัวชี้การอ้างอิงหรือตัววนซ้ำ) จะไม่ประสบปัญหานั้น เช่น (คงที่) บัฟเฟอร์จะถูกยกเลิกการแชร์เมื่อใดก็ได้หรือใช้ร่วมกันโดยไม่มีการอ้างอิงภายนอก ฉันคิดว่าคุณตั้งใจให้ความคิดเห็นเกี่ยวกับตัวอย่างนี้เป็นรายงานข้อผิดพลาดอย่างไม่เป็นทางการตามความคิดเห็นขออภัยเป็นอย่างยิ่งที่เข้าใจผิด! แต่อย่างที่คุณเห็นได้จากการพิจารณาการนำไปใช้งานตามที่ฉันอธิบายไว้ที่นี่ซึ่งทำงานได้ดีใน C ++ 11 เมื่อnoexceptข้อกำหนดถูกละเว้นตัวอย่างไม่ได้กล่าวถึงสิ่งที่เป็นทางการ ฉันสามารถให้รหัสได้ถ้าคุณต้องการ
ไชโยและ hth - Alf

7
หากคุณเลิกแชร์การเข้าถึงสตริงเกือบทุกครั้งคุณจะสูญเสียประโยชน์ทั้งหมดจากการแชร์ การใช้งาน COW จะต้องใช้งานได้จริงสำหรับไลบรารีมาตรฐานที่จะรบกวนการใช้งานดังstd::stringกล่าวและฉันสงสัยเป็นอย่างยิ่งว่าคุณสามารถแสดงสตริง COW ที่มีประโยชน์และมีประสิทธิภาพซึ่งตรงตามข้อกำหนดการยกเลิก C ++ 11 ดังนั้นฉันจึงยืนยันว่าnoexceptข้อกำหนดที่เพิ่มเข้ามาในนาทีสุดท้ายเป็นผลมาจากการห้ามสตริง COW ไม่ใช่เหตุผลพื้นฐาน N2668 ดูเหมือนชัดเจนดีเหตุใดคุณจึงปฏิเสธหลักฐานที่ชัดเจนเกี่ยวกับเจตนาของคณะกรรมการที่ระบุไว้ในนั้น
Jonathan Wakely

นอกจากนี้โปรดจำไว้ว่าdata()เป็นฟังก์ชันสมาชิก const ดังนั้นต้องปลอดภัยในการโทรพร้อมกันกับสมาชิก const อื่น ๆ และตัวอย่างเช่นการโทรdata()พร้อมกันกับเธรดอื่นโดยทำสำเนาสตริง ดังนั้นคุณจะต้องมีค่าใช้จ่ายทั้งหมดของ mutex สำหรับทุกการทำงานของสตริงแม้กระทั่ง const หรือความซับซ้อนของโครงสร้างที่นับการอ้างอิงที่ไม่สามารถเปลี่ยนแปลงได้โดยไม่ต้องล็อคและหลังจากนั้นคุณจะได้รับการแบ่งปันหากคุณไม่เคยแก้ไขหรือเข้าถึง สตริงของคุณจำนวนมากสตริงจำนวนมากจะมีการอ้างอิง - นับหนึ่ง โปรดระบุรหัสอย่าลังเลที่จะละเว้นnoexceptการค้ำประกัน
Jonathan Wakely

2
ตอนนี้ฉันค้นพบว่ามีbasic_stringสมาชิก129 ฟังก์ชันพร้อมฟังก์ชันฟรี ต้นทุนที่เป็นนามธรรม: โค้ดเวอร์ชัน zeroth ใหม่ที่ไม่ได้รับการปรับให้เหมาะสมนี้ช้าลง 50 ถึง 100% เมื่อใช้ทั้ง g ++ และ MSVC มันไม่ได้ทำเพื่อความปลอดภัยของเธรด ( shared_ptrฉันคิดว่าใช้ประโยชน์ได้ง่ายพอ) และเพียงพอที่จะรองรับการจัดเรียงพจนานุกรมเพื่อจุดประสงค์ในการกำหนดเวลา แต่ข้อบกพร่องของโมดูโลจะพิสูจน์จุดที่basic_stringอนุญาตให้มีการอ้างอิงได้ยกเว้นnoexceptข้อกำหนด C ++ github.com/alfps/In-principle-demo-of-ref-counted-basic_string
ไชโยและ hth - Alf

1
ขอให้เรายังคงอภิปรายนี้ในการแชท
Jonathan Wakely

20

CoW เป็นกลไกที่ยอมรับได้ในการสร้างสตริงที่เร็วขึ้น ... แต่ ...

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

เหตุผลอื่น ๆ คือตัว[]ดำเนินการจะส่งคืนข้อมูลสตริงให้คุณโดยไม่มีการป้องกันใด ๆ ให้คุณเขียนทับสตริงที่คนอื่นคาดว่าจะไม่เปลี่ยนแปลง เช่นเดียวกับและc_str()data()

Quick google บอกว่าโดยพื้นฐานแล้วการมัลติเธรดเป็นสาเหตุที่ไม่ได้รับอนุญาตอย่างมีประสิทธิภาพ (ไม่ชัดเจน)

ข้อเสนอกล่าวว่า:

ข้อเสนอ

เราขอเสนอให้ดำเนินการตัววนซ้ำและการเข้าถึงองค์ประกอบทั้งหมดพร้อมกันอย่างปลอดภัย

เรากำลังเพิ่มความเสถียรของการดำเนินการแม้ในรหัสต่อเนื่อง

การเปลี่ยนแปลงนี้ปิดกั้นการใช้งานแบบ copy-on-write

ติดตามโดย

การสูญเสียประสิทธิภาพที่ใหญ่ที่สุดเนื่องจากการเปลี่ยนจากการใช้งานแบบ copy-on-write คือการใช้หน่วยความจำที่เพิ่มขึ้นสำหรับแอปพลิเคชันที่มีสตริงการอ่านเป็นส่วนใหญ่ที่มีขนาดใหญ่มาก อย่างไรก็ตามเราเชื่อว่าสำหรับการใช้งานเชือกเหล่านั้นเป็นวิธีการแก้ปัญหาทางเทคนิคที่ดีกว่าและขอแนะนำให้พิจารณาข้อเสนอเชือกเพื่อรวมไว้ใน Library TR2

เชือกเป็นส่วนหนึ่งของ STLPort และ SGIs STL


2
ปัญหาตัวดำเนินการ [] ไม่ใช่ปัญหาจริงๆ ตัวแปร const ให้การป้องกันและตัวแปรที่ไม่ใช่ const จะมีตัวเลือกในการทำ CoW ในเวลานั้นเสมอ (หรือบ้ามากและตั้งค่าความผิดพลาดของเพจเพื่อเรียกใช้)
Christopher Smith

+1ไปที่ประเด็น
ไชโยและ hth - Alf

5
มันโง่มากที่ไม่รวมคลาส std :: cow_string ด้วย lock_buffer () ฯลฯ มีหลายครั้งที่ฉันรู้ว่าเธรดไม่ใช่ปัญหา บ่อยกว่าไม่จริง
Erik Aronesty

ฉันชอบข้อเสนอแนะของเชือก ig ทางเลือก ฉันสงสัยว่ามีทางเลือกและการใช้งานประเภทอื่น ๆ หรือไม่
Voltaire

5

จาก 21.4.2 basic_string constructor และตัวดำเนินการกำหนด [string.cons]

basic_string(const basic_string<charT,traits,Allocator>& str);

[ ... ]

2 เอฟเฟกต์ : สร้างออบเจ็กต์ของคลาสbasic_stringตามที่ระบุในตารางที่ 64 [... ]

ตารางที่ 64 เอกสารที่เป็นประโยชน์ว่าหลังจากสร้างวัตถุผ่านตัวสร้าง (สำเนา) นี้this->data()มีค่าดังนี้:

จุดที่องค์ประกอบ fi rst ของสำเนาที่จัดสรรของอาร์เรย์ซึ่งองค์ประกอบ fi rst ถูกชี้ไปที่ str.data ()

มีข้อกำหนดที่คล้ายกันสำหรับผู้สร้างอื่นที่คล้ายคลึงกัน


+1อธิบายว่า C ++ 11 (อย่างน้อยบางส่วน) ห้าม COW ได้อย่างไร
ไชโยและ hth - Alf

ขอโทษนะฉันเหนื่อย ไม่ได้อธิบายอะไรมากไปกว่าการเรียก. data () ต้องทริกเกอร์การคัดลอก COW หากมีการแชร์บัฟเฟอร์ในปัจจุบัน ยังคงเป็นข้อมูลที่มีประโยชน์ดังนั้นฉันจึงให้คะแนนโหวตเพิ่มขึ้น
ไชโยและ hth - Alf

1

เนื่องจากตอนนี้มีการรับประกันว่าสตริงจะถูกจัดเก็บอย่างต่อเนื่องกันและตอนนี้คุณได้รับอนุญาตให้นำตัวชี้ไปยังที่จัดเก็บข้อมูลภายในของสตริง (เช่น & str [0] ทำงานเหมือนกับที่ทำกับอาร์เรย์) จึงไม่สามารถสร้าง COW ที่เป็นประโยชน์ได้ การดำเนินงาน คุณจะต้องทำสำเนาสำหรับหลาย ๆ อย่างเกินไป แม้แต่การใช้operator[]หรือbegin()บนสตริงที่ไม่ใช่ const ก็ต้องใช้สำเนา


1
ฉันคิดว่าสตริงใน C ++ 11 ได้รับการรับรองว่าจะจัดเก็บติดต่อกัน
mfontanini

4
ในอดีตคุณต้องทำสำเนาในทุกสถานการณ์และมันก็ไม่ใช่ปัญหา ...
David Rodríguez - dribeas

@mfontanini ใช่ แต่ก่อนหน้านี้ไม่ได้
Dirk Holsopple

3
แม้ว่า C ++ 11 จะรับประกันว่าสตริงจะต่อเนื่องกัน แต่ก็มีมุมฉากกับการห้ามสตริง COW สตริง COW ของ GCC อยู่ติดกันดังนั้นการอ้างว่า"ไม่สามารถใช้งาน COW ที่เป็นประโยชน์ได้"ถือเป็นการหลอกลวง
Jonathan Wakely

1
@supercat ขอที่เก็บสำรอง (เช่นโทรc_str()) ต้องเป็น O (1) และไม่สามารถโยนได้และต้องไม่แนะนำการแข่งขันข้อมูลดังนั้นจึงเป็นเรื่องยากมากที่จะปฏิบัติตามข้อกำหนดเหล่านั้นหากคุณเชื่อมต่อกันอย่างเกียจคร้าน ในทางปฏิบัติทางเลือกเดียวที่เหมาะสมคือการจัดเก็บข้อมูลที่ต่อเนื่องกันเสมอ
Jonathan Wakely

1

basic_stringห้ามวัวใน C ++ 11 ขึ้นไปหรือไม่?

เกี่ยวกับ

ฉันถูกต้องที่ C ++ 11 ไม่ยอมรับการใช้งานตามวัวของstd::string?

ใช่.

เกี่ยวกับ

ถ้าเป็นเช่นนั้นข้อ จำกัด นี้มีการระบุไว้อย่างชัดเจนในมาตรฐานใหม่ (ที่ไหน) หรือไม่?

เกือบโดยตรงตามข้อกำหนดของความซับซ้อนคงที่สำหรับการดำเนินการหลายอย่างที่ต้องการ O ( n ) การคัดลอกข้อมูลสตริงทางกายภาพในการใช้งาน COW

ตัวอย่างเช่นสำหรับฟังก์ชันสมาชิก

auto operator[](size_type pos) const -> const_reference;
auto operator[](size_type pos) -> reference;

…ซึ่งในการใช้งาน COW จะทริกเกอร์การคัดลอกข้อมูลสตริงทั้งสองเพื่อยกเลิกการแชร์ค่าสตริงมาตรฐาน C ++ 11 ต้องการ

C ++ 11 §21.4.5 / 4 :

" ความซับซ้อน:เวลาคงที่

…ซึ่งออกกฎการคัดลอกข้อมูลดังกล่าวและด้วยเหตุนี้ COW

C ++ 03 ได้รับการสนับสนุนการใช้งาน COW โดยไม่ได้มีความต้องการที่ซับซ้อนอย่างต่อเนื่องเหล่านี้และโดยภายใต้เงื่อนไขที่เข้มงวดบางอย่างที่ช่วยให้โทรไปoperator[](), at(), begin(), rbegin(), end()หรือrend()อ้างอิงโมฆะตัวชี้และ iterators หมายถึงรายการสตริงเช่นจะอาจเกิดขึ้น การคัดลอกข้อมูล COW การสนับสนุนนี้ถูกลบออกใน C ++ 11


ห้ามวัวผ่านกฎการยกเลิก C ++ 11 ด้วยหรือไม่

ในคำตอบอื่นซึ่งในขณะที่เขียนถูกเลือกให้เป็นวิธีแก้ปัญหาและซึ่งได้รับการโหวตอย่างมากและเห็นได้ชัดว่ามีการยืนยันว่า

"สำหรับสตริง COW การเรียกแบบไม่const operator[]ต้องทำสำเนา (และการอ้างอิงที่ไม่ถูกต้อง) ซึ่งไม่อนุญาตโดยย่อหน้า [ที่ยกมา] ด้านบน [C ++ 11 §21.4.1 / 6] ดังนั้นจึงไม่ถูกกฎหมายอีกต่อไปที่จะมีสตริง COW ใน C ++ 11

การยืนยันนั้นไม่ถูกต้องและทำให้เข้าใจผิดในสองวิธีหลัก:

  • มันบ่งชี้อย่างไม่ถูกต้องว่าเฉพาะผู้เข้าถึงที่ไม่ใช่constรายการเท่านั้นที่ต้องทริกเกอร์การคัดลอกข้อมูล COW
    แต่ผู้เข้าถึงconstรายการจำเป็นต้องทริกเกอร์การคัดลอกข้อมูลเนื่องจากอนุญาตให้รหัสไคลเอ็นต์สร้างการอ้างอิงหรือตัวชี้ที่ (ใน C ++ 11) ไม่อนุญาตให้ยกเลิกการใช้งานในภายหลังผ่านการดำเนินการที่สามารถทริกเกอร์การคัดลอกข้อมูล COW
  • สันนิษฐานอย่างไม่ถูกต้องว่าการคัดลอกข้อมูล COW อาจทำให้การอ้างอิงไม่ถูกต้อง
    แต่ในการนำไปใช้งานที่ถูกต้องการคัดลอกข้อมูล COW การยกเลิกการแชร์ค่าสตริงจะกระทำ ณ จุดหนึ่งก่อนที่จะมีการอ้างอิงใด ๆ ที่อาจทำให้ไม่ถูกต้องได้

หากต้องการดูว่าการใช้งาน C ++ 11 COW ที่ถูกต้องbasic_stringจะทำงานอย่างไรเมื่อข้อกำหนด O (1) ที่ทำให้ไม่ถูกต้องถูกละเว้นให้นึกถึงการใช้งานที่สตริงสามารถสลับระหว่างนโยบายการเป็นเจ้าของได้ อินสแตนซ์สตริงเริ่มต้นด้วย Policy Sharable เมื่อใช้นโยบายนี้แล้วจะไม่มีการอ้างอิงรายการภายนอก อินสแตนซ์สามารถเปลี่ยนไปใช้นโยบายที่ไม่ซ้ำกันได้และจะต้องดำเนินการดังกล่าวเมื่อมีการสร้างการอ้างอิงรายการเช่นด้วยการเรียกไปที่.c_str()(อย่างน้อยหากสร้างตัวชี้ไปยังบัฟเฟอร์ภายใน) ในกรณีทั่วไปของอินสแตนซ์หลายอินสแตนซ์ร่วมกันเป็นเจ้าของค่าสิ่งนี้เกี่ยวข้องกับการคัดลอกข้อมูลสตริง หลังจากนั้นการเปลี่ยนไปใช้นโยบายที่ไม่ซ้ำกันอินสแตนซ์จะเปลี่ยนกลับไปเป็นแชร์ได้โดยการดำเนินการที่ทำให้การอ้างอิงทั้งหมดเป็นโมฆะเช่นการมอบหมาย

ดังนั้นในขณะที่ข้อสรุปของคำตอบนั้นสตริง COW ถูกตัดออกนั้นถูกต้อง แต่เหตุผลที่เสนอนั้นไม่ถูกต้องและทำให้เข้าใจผิดอย่างมาก

ฉันสงสัยว่าสาเหตุของความเข้าใจผิดนี้เป็นบันทึกที่ไม่เป็นบรรทัดฐานในภาคผนวก C ของ C ++ 11:

C ++ 11 §C.2.11 [diff.cpp03.strings] ประมาณ§21.3:

การเปลี่ยนแปลง : basic_stringข้อกำหนดไม่อนุญาตให้ใช้สตริงที่นับการอ้างอิงอีกต่อไป
เหตุผล:การไม่ถูกต้องแตกต่างกันเล็กน้อยกับสตริงที่นับการอ้างอิง การเปลี่ยนแปลงนี้ทำให้พฤติกรรม (sic) เป็นปกติสำหรับมาตรฐานสากลนี้
ผลกระทบต่อคุณสมบัติดั้งเดิม:โค้ด C ++ 2003 ที่ถูกต้องอาจทำงานแตกต่างกันในมาตรฐานสากลนี้

นี่คือเหตุผลที่อธิบายถึงสาเหตุหลักว่าเหตุใดจึงตัดสินใจลบการสนับสนุนพิเศษ C ++ 03 COW เหตุผลนี้เหตุผลที่ไม่ได้เป็นวิธีมาตรฐานได้อย่างมีประสิทธิภาพไม่อนุญาตให้ดำเนินการ COW มาตรฐานไม่อนุญาต COW ผ่านข้อกำหนด O (1)

ในระยะสั้น, C ++ 11 std::basic_stringกฎโมฆะไม่ออกกฎการดำเนินงานของวัว แต่พวกเขาแยกแยะการใช้งาน COW สไตล์ C ++ 03 ที่มีประสิทธิภาพอย่างสมเหตุสมผลเช่นเดียวกับการใช้งานไลบรารีมาตรฐานของ g ++ อย่างน้อยหนึ่งอย่าง การสนับสนุน C ++ 03 COW แบบพิเศษช่วยให้สามารถใช้งานได้อย่างมีประสิทธิภาพโดยเฉพาะอย่างยิ่งการใช้ตัวเข้าถึงconstรายการโดยมีค่าใช้จ่ายของกฎที่ละเอียดอ่อนและซับซ้อนสำหรับการยกเลิกการใช้งาน:

C ++ 03 §21.3 / 5ซึ่งรวมถึงการสนับสนุน COW "การโทรครั้งแรก":

"การอ้างอิงตัวชี้และตัวทำซ้ำที่อ้างถึงองค์ประกอบของbasic_stringลำดับอาจไม่ถูกต้องโดยการใช้งานต่อไปนี้ของbasic_stringวัตถุนั้น:
- เป็นอาร์กิวเมนต์สำหรับฟังก์ชันที่ไม่ใช่สมาชิกswap()(21.3.7.8), operator>>()(21.3.7.9) และgetline()(21.3. 7.9)
- เป็นข้อโต้แย้งbasic_string::swap()ของ
- ฟังก์ชั่นการโทรdata()และc_str()สมาชิก
- โทรที่ไม่ใช่constสมาชิกฟังก์ชันยกเว้นoperator[](), at(), begin(), rbegin(), และend() - ภายหลังการใด ๆ ของการใช้งานดังกล่าวข้างต้นยกเว้นรูปแบบของและที่กลับ iterators, สายแรกที่ไม่ใช่สมาชิกฟังก์ชัน, , , ,rend()
insert()erase()constoperator[]()at()begin()rbegin()end()หรือrend().

กฎเหล่านี้ซับซ้อนและละเอียดอ่อนมากจนฉันสงสัยว่าโปรแกรมเมอร์หลายคนสามารถสรุปได้อย่างแม่นยำ ฉันไม่สามารถ.


จะเกิดอะไรขึ้นถ้าข้อกำหนด O (1) ถูกเพิกเฉย

หากoperator[]ไม่คำนึงถึงข้อกำหนดเวลาคงที่ของ C ++ 11 ในเช่นนั้น COW สำหรับbasic_stringอาจเป็นไปได้ในทางเทคนิค แต่ยากที่จะนำไปใช้

การดำเนินการที่สามารถเข้าถึงเนื้อหาของสตริงโดยไม่ต้องคัดลอกข้อมูล COW ได้แก่ :

  • การเชื่อมต่อผ่าน+.
  • เอาต์พุตผ่าน<<.
  • การใช้basic_stringอาร์กิวเมนต์เป็นฟังก์ชันไลบรารีมาตรฐาน

เนื่องจากไลบรารีมาตรฐานได้รับอนุญาตให้อาศัยความรู้และโครงสร้างเฉพาะในการนำไปใช้งาน

นอกจากนี้การใช้งานสามารถนำเสนอฟังก์ชันที่ไม่ได้มาตรฐานต่างๆสำหรับการเข้าถึงเนื้อหาสตริงโดยไม่ต้องเรียกใช้การคัดลอกข้อมูล COW

ปัจจัยที่ทำให้เกิดความซับซ้อนหลักคือในbasic_stringการเข้าถึงรายการC ++ 11 ต้องทริกเกอร์การคัดลอกข้อมูล (ยกเลิกการแบ่งปันข้อมูลสตริง) แต่จำเป็นต้องไม่โยนเช่น C ++ 11 §21.4.5 / 3“ Throws: Nothing” ดังนั้นจึงไม่สามารถใช้การจัดสรรแบบไดนามิกธรรมดาเพื่อสร้างบัฟเฟอร์ใหม่สำหรับการคัดลอกข้อมูล COW วิธีหนึ่งในการแก้ปัญหานี้คือการใช้ฮีปพิเศษที่สามารถจองหน่วยความจำได้โดยไม่ต้องจัดสรรจริงจากนั้นจองจำนวนที่จำเป็นสำหรับการอ้างอิงเชิงตรรกะแต่ละค่าเป็นสตริง การจองและยกเลิกการจองในฮีปอาจเป็นเวลาคงที่ O (1) และการจัดสรรจำนวนเงินที่จองไว้แล้วสามารถnoexcept. เพื่อให้เป็นไปตามข้อกำหนดของมาตรฐานด้วยวิธีนี้ดูเหมือนว่าจะต้องมีฮีปตามการจองพิเศษต่อผู้จัดสรรที่แตกต่างกัน


หมายเหตุ:
¹ตัวเข้าถึงconstรายการทริกเกอร์การคัดลอกข้อมูล COW เนื่องจากอนุญาตให้โค้ดไคลเอ็นต์รับข้อมูลอ้างอิงหรือตัวชี้ไปยังข้อมูลซึ่งไม่ได้รับอนุญาตให้ทำให้เป็นโมฆะโดยการคัดลอกข้อมูลในภายหลังที่ทริกเกอร์โดยเช่นตัวเข้าถึงที่ไม่ใช่constรายการ


3
" ตัวอย่างของคุณเป็นตัวอย่างที่ดีของการใช้งาน C ++ 11 ที่ไม่ถูกต้องอาจถูกต้องสำหรับ C ++ 03" ใช่ที่จุดของตัวอย่างที่ แสดงสตริง COW ที่ถูกกฎหมายใน C ++ 03 เนื่องจากไม่ทำลายกฎการทำให้ไม่ถูกต้องของตัวทำซ้ำแบบเก่าและไม่ถูกกฎหมายใน C ++ 11 เนื่องจากจะละเมิดกฎการทำให้ไม่ถูกต้องของตัวทำซ้ำใหม่ และยังขัดแย้งกับข้อความที่ฉันยกมาในความคิดเห็นด้านบน
Jonathan Wakely

2
หากคุณบอกว่าแชร์ได้ไม่ได้แชร์ในตอนแรกฉันจะไม่เถียง การพูดอะไรบางอย่างในตอนแรกเป็นเรื่องที่สับสน แชร์กับตัวเอง? นั่นไม่ใช่ความหมายของคำ แต่ฉันขอย้ำ: ความพยายามของคุณที่จะโต้แย้งว่ากฎการทำให้ไม่ถูกต้องของตัววนซ้ำ C ++ 11 ไม่ได้ห้ามสตริง COW สมมุติบางตัวที่ไม่เคยใช้ในทางปฏิบัติ (และจะมีประสิทธิภาพที่ยอมรับไม่ได้) เมื่อพวกเขาห้ามประเภทของสตริง COW อย่างแน่นอน ที่ใช้ในทางปฏิบัตินั้นค่อนข้างเป็นวิชาการและไม่มีจุดหมาย
Jonathan Wakely

5
สตริง COW ที่คุณเสนอนั้นน่าสนใจ แต่ฉันไม่แน่ใจว่ามันจะมีประโยชน์แค่ไหน จุดของสตริง COW คือคัดลอกข้อมูลสตริงในกรณีที่เขียนสตริงทั้งสองเท่านั้น การใช้งานที่แนะนำของคุณจำเป็นต้องมีการคัดลอกเมื่อการดำเนินการอ่านที่ผู้ใช้กำหนดเกิดขึ้น แม้ว่าคอมไพเลอร์จะรู้ว่าเป็นเพียงการอ่าน แต่ก็ยังต้องคัดลอก นอกจากนี้การคัดลอกสตริงที่ไม่ซ้ำจะส่งผลให้เกิดการคัดลอกข้อมูลสตริง (น่าจะเป็นสถานะที่แชร์ได้) ซึ่งทำให้ COW ค่อนข้างไม่มีจุดหมายอีกครั้ง ดังนั้นหากไม่มีการรับประกันความซับซ้อนคุณสามารถเขียน ... สตริง COW ที่เส็งเคร็งจริงๆ
Nicol Bolas

2
ดังนั้นในขณะที่คุณถูกต้องในทางเทคนิคที่รับประกันความซับซ้อนเป็นสิ่งที่ป้องกันไม่ให้คุณเขียนCOW ในรูปแบบใด ๆแต่ก็เป็น [basic.string] / 5 ที่ป้องกันไม่ให้คุณเขียนสตริง COW ในรูปแบบที่มีประโยชน์อย่างแท้จริง
Nicol Bolas

4
@JonathanWakely: (1) คำพูดของคุณไม่ใช่คำถาม นี่คือคำถาม: "ฉันถูกต้องหรือไม่ที่ C ++ 11 ไม่ยอมรับการใช้งานตามมาตรฐาน COW ของ std :: string ถ้าเป็นเช่นนั้นข้อ จำกัด นี้มีการระบุไว้อย่างชัดเจนในมาตรฐานใหม่หรือไม่ (ที่ไหน)” (2) ความคิดเห็นของคุณที่ว่า COW std::stringเมื่อไม่คำนึงถึงข้อกำหนด O (1) จะไม่มีประสิทธิภาพเป็นความคิดเห็นของคุณ ฉันไม่รู้ว่าการแสดงจะเป็นอย่างไร แต่ฉันคิดว่าการยืนยันนั้นถูกนำมาใช้มากกว่าสำหรับความรู้สึกของมันสำหรับความรู้สึกที่สื่อถึงมากกว่าความเกี่ยวข้องใด ๆ กับคำตอบนี้
ไชโยและ hth - Alf

0

ฉันมักจะสงสัยเกี่ยวกับวัวที่ไม่เปลี่ยนรูป: เมื่อสร้างวัวแล้วฉันสามารถเปลี่ยนแปลงได้โดยการมอบหมายจากวัวตัวอื่นเท่านั้นดังนั้นจึงเป็นไปตามมาตรฐาน

ฉันมีเวลาลองวันนี้สำหรับการทดสอบเปรียบเทียบอย่างง่าย: แผนที่ขนาด N ที่คีย์ด้วยสตริง / วัวโดยทุกโหนดถือชุดของสตริงทั้งหมดในแผนที่ (เรามีจำนวนวัตถุ NxN)

ด้วยสตริงที่มีขนาด ~ 300 ไบต์และวัว N = 2000 จะเร็วกว่าเล็กน้อยและใช้หน่วยความจำน้อยกว่า ดูด้านล่างขนาดเป็น kbs รัน b อยู่กับวัว

~/icow$ ./tst 2000
preparation a
run
done a: time-delta=6 mem-delta=1563276
preparation b
run
done a: time-delta=3 mem-delta=186384
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.