01
ใช้งานตัวดำเนินการไปป์ไลน์02
ใช้ตัวอักษรที่กำหนดเอง _f
03
ใช้ print
และเชี่ยวชาญ std::formatter
04
แก้ไขเทมเพลตคลาสที่กำหนดเพื่อให้มี ID ที่แตกต่างกันสำหรับอินสแตนซ์แต่ละประเภทที่แตกต่างกัน05
ใช้ประเภท scope_guard
06
ของการเริ่มต้น std::atomic
07
throw new MyException
08
ของคู่มือการสืบทอด array
09
ปัญหาการค้นหาชื่อ10
สำรวจสมาชิกข้อมูลคลาสใด ๆC++17
C++20
11
ปัญหาเกี่ยวกับ emplace_back()
12
make_vector()
13
return std::move
14
แก้ไขวัตถุที่ประกาศในเนมสเปซด้วยวิธีการพิเศษ15
16
มาโครสำหรับสร้างเทมเพลตฟังก์ชันถ่ายโอนการแสดงการบ้านของลูเธอร์
การส่ง PR ไม่ควรเปลี่ยนแปลง README
ปัจจุบัน โปรดส่งงานไปที่ src群友提交
ตัวอย่างเช่น หากคุณต้องการส่งงานแรก:
คุณควรสร้างไฟล์ .md
หรือ .cpp
ของคุณเองใน src群友提交第01题
ชื่อไฟล์ควรตั้งชื่อตาม ID กลุ่มการสื่อสารของคุณเอง (หรือชื่อผู้ใช้ GitHub เพื่อให้คุณสามารถค้นหาตัวเองได้ง่าย)
ข้อกำหนดทั่วไป สำหรับการตอบคำถามมีดังนี้ (โปรดคำนึงถึงข้อกำหนดเพิ่มเติมสำหรับคำถาม):
main
คุณต้องไม่หยุดไม่ให้มันทำงาน (หมายถึงอย่าใช้ประโยชน์จากมัน)01
ใช้งานตัวดำเนินการไปป์ไลน์ วันที่: 2023/7/21
ผู้ถาม: mq白
รับรหัส:
int main (){
std::vector v{ 1 , 2 , 3 };
std::function f {[]( const int & i) {std::cout << i << ' ' ; } };
auto f2 = []( int & i) {i *= i; };
v | f2 | f;
}
1 4 9
ตอบโดย: andyli
# include < algorithm >
# include < vector >
# include < functional >
# include < iostream >
template < typename R, typename F>
auto operator |(R&& r, F&& f) {
for ( auto && x: r)
f (x);
return r;
}
int main () {
std::vector v{ 1 , 2 , 3 };
std::function f{[]( const int & i) { std::cout << i << ' ' ; }};
auto f2 = []( int & i) { i *= i; };
v | f2 | f;
}
ก็เป็นเรื่องปกติ ไม่มีปัญหา
ตอบโดย : mq松鼠
# include < iostream >
# include < vector >
# include < functional >
auto operator | (std::vector< int >&& v,std::function< void ( const int &)> f){
for ( auto &i:v){
f (i);
}
return v;
}
auto operator | (std::vector< int >& v,std::function< void ( int &)> f){
for ( auto &i:v){
f (i);
}
return v;
}
int main (){
std::vector v{ 1 , 2 , 3 };
std::function f {[]( const int & i) {std::cout << i << ' n ' ; } };
auto f2 = []( int & i) {i *= i; };
v | f2 | f;
}
ความคิดเห็น: ถ้าฉันไม่มีอะไรทำ ฉันจะเขียนโอเวอร์โหลดเพิ่มและใส่เฟรมมัน
template < typename U, typename F>
requires std::regular_invocable<F, U&> //可加可不加,不会就不加
std::vector<U>& operator |(std::vector<U>& v1, F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
โดยไม่ต้องใช้เทมเพลต :
std::vector< int >& operator |(std::vector< int >& v1, const std::function< void ( int &)>& f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
แทนที่จะใช้ขอบเขต for
ให้ใช้เทมเพลตฟังก์ชันชวเลข C ++ 20:
std::vector< int >& operator |( auto & v1, const auto & f) {
std::ranges::for_each (v1, f);
return v1;
}
กระบวนทัศน์ของคำตอบอื่นๆ ไม่มีอะไรมากไปกว่าการเปลี่ยนแปลงเหล่านี้ ไม่จำเป็นต้องเขียนใหม่อีกครั้ง
แน่นอนว่าเราจำเป็นต้องโอเวอร์โหลดตัวดำเนินการไปป์ไลน์ | ตามแบบฟอร์มการโทรของเรา v | f2 | f
ห่วงโซ่การโทรนี้ และจากผลการทำงานที่กำหนด เราสามารถรู้ได้ว่าฟังก์ชันที่โอเวอร์โหลดควรส่งคืนการอ้างอิงถึง v และ จะได้รับการแก้ไข v | f2
เรียก operator |
. ใช้ f2 เพื่อสำรวจทุกองค์ประกอบใน v จากนั้นส่งคืนการอ้างอิงของ v จากนั้น |
template < typename U, typename F>
requires std::regular_invocable<F, U&> //我们可以认为对模板形参U,F满足std::regular_invocable的约束
หากคุณไม่เคยเผชิญกับการแสดงออกถึงข้อจำกัด ก็ไม่สำคัญ เราจะแนะนำพวกเขาสั้นๆ ด้านล่างนี้
นิพจน์ที่ต้องการนั้นเหมือนกับฟังก์ชันที่ส่งคืนบูล และ U และ F จะถูกกรอกในรายการพารามิเตอร์ที่แท้จริงของ std::regular_invocable เป็นประเภท ตราบใดที่ U และ F เป็นประเภทที่ตรงตามนิพจน์ ก็จะส่งกลับค่าจริง หากไม่เป็นเช่นนั้น จะส่งคืนค่าเท็จ ซึ่งเรียกว่า "ไม่พอใจข้อจำกัด" ประเภทที่ไม่เป็นไปตามข้อจำกัดจะไม่เรียกใช้โค้ดที่ตามมาโดยธรรมชาติ
สำหรับ std::regular_invocable เราสามารถคิดว่ามันเป็นคู่ของแต่ละค่าประเภท U ไม่ว่าเราจะเรียกใช้ฟังก์ชัน F ได้หรือไม่ นั่นคือ call std::invoke
นี่เทียบเท่ากับการที่เราจินตนาการถึงรันไทม์ ณ เวลาคอมไพล์ และจินตนาการว่า U สามารถรัน F ณ รันไทม์ได้หรือไม่ ถ้าเป็นเช่นนั้นก็เป็นไปตามข้อจำกัด
ส่วนของฟังก์ชันนั้นเรียบง่ายมาก
std::vector<U>& operator |(std::vector<U>& v1, const F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
นิพจน์ช่วง for (auto& i : v1)
เป็นเหมือน for(auto i=v.begin();i!=v.end();++i){f(*i)}
: เรามี เวกเตอร์ (range ) ใช้ฟังก์ชัน f หนึ่งครั้งกับแต่ละองค์ประกอบในนั้น กลับสู่ v1 ตามปกติ
หากเราไม่ใช้เทมเพลต รายการพารามิเตอร์อย่างเป็นทางการของเราต้องใช้ std::function เพื่อจับฟังก์ชันที่เราใช้:
การใช้ f กับสมาชิกแต่ละคนในช่วงไม่จำเป็นต้องมีค่าตอบแทน และต้องมีการแก้ไของค์ประกอบในช่วง ดังนั้นพารามิเตอร์ตัวที่สองจึงเป็น std::function<void(int&)>
และเราไม่จำเป็นต้องแก้ไขหรือคัดลอกฟังก์ชัน f ที่ส่งเข้ามา ดังนั้นจึงเป็นนิสัยที่ดีที่จะเพิ่มข้อจำกัด const
ในทำนองเดียวกัน เราไม่สามารถใช้ range for แต่ใช้ std::ranges::for_each(v1, f);
ง่ายกว่า นั่นคือใช้ฟังก์ชัน f หนึ่งครั้งกับแต่ละองค์ประกอบในช่วง v1 ดังที่กล่าวข้างต้น
สำหรับรูปแบบการใช้เทมเพลต เราสามารถใช้เทมเพลตฟังก์ชันตัวย่อ C++20 กล่าวโดยสรุป ตัวยึดตำแหน่งอัตโนมัติในรายการพารามิเตอร์ฟังก์ชันจะผนวกพารามิเตอร์เทมเพลตจำลองเข้ากับรายการพารามิเตอร์เทมเพลต แบบฟอร์มเทมเพลตเริ่มต้นสามารถเขียนได้เป็น
std::vector< int >& operator |( auto & v1, const auto & f)
มันก็เหมือนกับรูปแบบเดิม
02
ใช้ตัวอักษรที่กำหนดเอง _f
วันที่: 2023/7/22
ผู้ถาม: mq白
รับรหัส:
int main (){
std::cout << "乐 :{} * n " _f ( 5 );
std::cout << "乐 :{0} {0} * n " _f ( 5 );
std::cout << "乐 :{:b} * n " _f ( 0b01010101 );
std::cout << " {:*<10} " _f ( "卢瑟" );
std::cout << ' n ' ;
int n{};
std::cin >> n;
std::cout << " π:{:.{}f} n " _f (std::numbers::pi_v< double >, n);
}
乐 :5 *
乐 :5 5 *
乐 :1010101 *
卢瑟******
6
π:3.141593
6
คืออินพุตและกำหนด
ตอบโดย: andyli
# include < format >
# include < iostream >
# include < string_view >
# include < string >
namespace impl {
struct Helper {
const std::string_view s;
Helper ( const char * s, std:: size_t len): s(s, len) {}
template < typename ... Args>
std::string operator ()(Args&&... args) const {
return std::vformat (s, std::make_format_args (args...));
}
};
} // namespace impl
impl::Helper operator " " _f( const char * s, std:: size_t len) noexcept {
return {s, len};
}
int main () {
std::cout << "乐 :{} * n " _f ( 5 );
std::cout << "乐 :{0} {0} * n " _f ( 5 );
std::cout << "乐 :{:b} * n " _f ( 0b01010101 );
std::cout << " {:*<10} " _f ( "卢瑟" );
std::cout << ' n ' ;
int n{};
std::cin >> n;
std::cout << " π:{:.{}f} n " _f (std::numbers::pi_v< double >, n);
}
constexpr auto operator " " _f( const char * fmt, size_t ) {
return [=]< typename ... T>(T&&... Args) { return std::vformat (fmt, std::make_format_args (Args...)); };
}
เราจำเป็นต้องใช้ตัวอักษรที่ผู้ใช้กำหนด C++11 และ ""_f
เป็นตัวอักษรที่ผู้ใช้กำหนดทุกประการ
อย่างไรก็ตาม รายการพารามิเตอร์ที่เป็นทางการของ ตัวดำเนินการตามตัวอักษร (ฟังก์ชันที่เรียกโดยตัวดำเนินการตามตัวอักษรที่ผู้ใช้กำหนดเรียกว่าตัวดำเนินการตามตัวอักษร) มีข้อจำกัดบางประการ สิ่งที่เราต้องการคือรายการพารามิเตอร์ที่เป็นทางการ เช่น const char *, std::size_t
ซึ่ง อนุญาตให้ทำเช่นนี้ได้ จำเป็นต้องปรับแต่งประเภทการส่งคืนของตัวดำเนินการตามตัวอักษร และประเภทนี้จำเป็นต้องโอเวอร์โหลด operator()
ภายในเพื่อให้เป็นไปตามข้อกำหนดข้างต้นเพื่อให้เรียกว่าฟังก์ชันเหมือน
ไปทีละขั้นตอน:
void operator " " _test( const char * str, std:: size_t ){
std::cout << str << ' n ' ;
}
" luse " _test; //调用了字面量运算符,打印 luse
std:: size_t operator " " _test( const char * , std:: size_t len){
return len;
}
std:: size_t len = " luse " _test; //调用了字面量运算符,返回 luse 的长度 4
ตัวอย่างการใช้งานทั้งสองของโค้ดข้างต้นแสดงการใช้งานพื้นฐานของตัวอักษรที่ผู้ใช้กำหนด ให้ความสนใจเป็นพิเศษกับย่อหน้าที่สอง ค่าส่งคืน หากคุณต้องการเรียกมันว่า "xxx"_f(xxx)
คุณต้องทำอะไรบางอย่างกับประเภทการคืนสินค้า
struct X {
std:: size_t operator ()(std:: size_t n) const {
return n;
}
};
X operator " " _test( const char * , std:: size_t ){
return {};
}
std::cout<< "无意义" _test( 1 ); //打印 1
โค้ดง่ายๆ ข้างต้นกรอกแบบฟอร์มการโทรที่เราต้องการได้อย่างสมบูรณ์แบบ จากนั้นก็ถึงเวลาที่จะต้องทำหน้าที่ตามที่คำถามต้องการให้สมบูรณ์ วิธีที่ง่ายที่สุดคือใช้ไลบรารีรูปแบบ C++20 โดยตรงสำหรับการจัดรูปแบบ
namespace impl {
struct Helper {
const std::string_view s;
Helper ( const char * s, std:: size_t len): s(s, len) {}
template < typename ... Args>
std::string operator ()(Args&&... args) const {
return std::vformat (s, std::make_format_args (args...));
}
};
} // namespace impl
impl::Helper operator " " _f( const char * s, std:: size_t len) noexcept {
return {s, len};
}
operator""_f
นั้นง่ายมาก มันถูกใช้เพื่อสร้าง impl::Helper
จากพารามิเตอร์ขาเข้า (รูปแบบสตริง) และความยาวแล้วส่งคืน ประเภท Helper
ใช้ string_view
เป็นสมาชิกข้อมูลเพื่อจัดเก็บสตริงรูปแบบสำหรับการจัดรูปแบบในภายหลัง
โฟกัสอยู่ที่ operator()
เท่านั้น เป็นเทมเพลตพารามิเตอร์ตัวแปร ซึ่งใช้เพื่อรับประเภทและจำนวนพารามิเตอร์ใดๆ ที่เราส่งผ่าน จากนั้นจึงส่งคืนสตริงที่จัดรูปแบบแล้ว
สิ่งที่ใช้ที่นี่คือ std::vformat
สำหรับการจัดรูปแบบ พารามิเตอร์แรกคือสตริงรูปแบบ นั่นคือกฎที่เราต้องการจัดรูปแบบตามกฎ พารามิเตอร์ตัวที่สองคือพารามิเตอร์ที่จะจัดรูปแบบ แต่เราไม่มีทางทำโดยตรง ขยายแพ็คเกจพารามิเตอร์อย่างเป็นทางการ จริงๆ แล้วประเภทของพารามิเตอร์ตัวที่สองคือ std::format_args
เราต้องใช้ฟังก์ชัน std::make_format_args
เพื่อส่งผ่านพารามิเตอร์ของเรา ซึ่งจะส่งคืนประเภท std::format_args
จริงๆ แล้ว มันเทียบเท่ากับการแปลงซึ่งสมเหตุสมผล
แต่เห็นได้ชัดว่าคำตอบมาตรฐานไม่เป็นเช่นนั้น และสามารถทำให้ง่ายขึ้นได้โดยปล่อยให้ ""_f
ส่งคืนนิพจน์แลมบ์ดา
03
ใช้ print
และเชี่ยวชาญ std::formatter
วันที่: 2023/7/24
ผู้ถาม: mq白
การนำ print
ไปใช้ หากคุณทำภารกิจก่อนหน้านี้ ฉันเชื่อว่านี่เป็นเรื่องง่าย แบบฟอร์มการโทรที่จำเป็นคือ:
print (格式字符串,任意类型和个数的符合格式字符串要求的参数)
struct Frac {
int a, b;
};
ด้วยประเภทที่กำหนดเอง Frace
ขอการสนับสนุน
Frac f{ 1 , 10 };
print ( " {} " , f); // 结果为1/10
1/10
ห้ามเขียนโปรแกรมเชิงผลลัพธ์ การใช้มาโคร ฯลฯ โดยมีค่าสูงสุดคือ B
(อ้างอิงถึงการประเมิน) งานมอบหมายนี้จะตรวจสอบและเรียนรู้ไลบรารี format
เป็นหลัก
เคล็ดลับ: std::formatter
วิธีที่ดีที่สุดคือส่งโค้ดพร้อมภาพหน้าจอของสามแพลตฟอร์มที่รวบรวมทางออนไลน์ เช่น:
template <>
struct std ::formatter<Frac>:std::formatter< char >{
auto format ( const auto & frac, auto & ctx) const { // const修饰是必须的
return std::format_to (ctx. out (), " {}/{} " , frac. a , frac. b );
}
};
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
เราเพียงแค่สนับสนุนแบบฟอร์ม ที่กำหนดโดยคำถาม และเชี่ยวชาญ std::formatter
หากเราต้องการสนับสนุนการจัดรูปแบบเช่น {:6}
ก็เห็นได้ชัดว่าเป็นไปไม่ได้ ความเชี่ยวชาญพิเศษและแบบฟอร์มที่สนับสนุนโดย std::formatter
สามารถพบได้ใน เอกสารประกอบ ความเชี่ยวชาญพิเศษบางอย่างได้ถูกเขียนขึ้นก่อนหน้านี้ ใน Cookbook มีความเชี่ยวชาญพิเศษสำหรับ std::ranges::range
และ std::tuple
ซึ่งรองรับทุกรูปแบบ
การใช้งานการพิมพ์นั้นง่ายมาก เราเพียงแค่ต้องปฏิบัติตามแนวคิดของคำถามที่สอง สำหรับสตริงที่จัดรูปแบบ ให้ใช้ std::string_view เป็นพารามิเตอร์ทางการตัวแรก นอกจากนี้ หากจำเป็นต้องใช้พารามิเตอร์และตัวเลขใด ๆ แพ็คเกจพารามิเตอร์อย่างเป็นทางการ
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
การเรียก vformat
ด้วยวิธีนี้จะส่งคืนสตริง ซึ่งสามารถส่งออกได้โดยตรงโดยใช้ cout
เกี่ยวกับความเชี่ยวชาญพิเศษของ std::formatter
แบบกำหนดเอง สิ่งที่เราจำเป็นต้องรู้คือ: หากคุณต้องการปรับแต่งความเชี่ยวชาญพิเศษของเทมเพลต std::formatter คุณจะต้องมีฟังก์ชันสองอย่าง ได้แก่ parse และ format
แยกวิเคราะห์ ใช้ในการประมวลผลคำอธิบายรูปแบบและตั้งค่าตัวแปรสมาชิกที่เกี่ยวข้อง สำหรับคำถามนี้ เราไม่จำเป็นต้องประสบปัญหาในการใช้ฟังก์ชันสมาชิกนี้
เราเลือกที่จะสืบทอดฟังก์ชัน การแยกวิเคราะห์ ของ std::formatter<char>
และใช้ฟังก์ชัน รูปแบบอย่าง อิสระ หากคุณไม่เข้าใจไวยากรณ์ของความเชี่ยวชาญพิเศษด้านเทมเพลต โปรดอ่านความเชี่ยวชาญพิเศษด้านเทมเพลต
template <>
struct std ::formatter<Frac> : std::formatter< char > {
auto format ( const auto & frac, auto & ctx) const { // const修饰是必须的
return std::format_to (ctx. out (), " {}/{} " , frac. a , frac. b );
}
};
นอกจากนี้เรายังใช้ auto เป็นเทมเพลตฟังก์ชันชวเลขสำหรับตัวยึดตำแหน่ง สำหรับฟังก์ชัน รูปแบบ พารามิเตอร์ แรกคือคลาสที่กำหนดเองที่เราส่งผ่าน และพารามิเตอร์ตัวที่สอง ( ctx ) คืออักขระรูปแบบที่เราต้องการส่งผ่านไปยังตัววนซ้ำเอาต์พุต std::format_to
. สตริง
ในส่วนของฟังก์ชัน เราจะส่งคืนผลลัพธ์ของนิพจน์การเรียก std::format_to()
โดยตรง
ในบรรดาพารามิเตอร์ของฟังก์ชัน ctx.out()
เป็นตัววนซ้ำเอาต์พุต พารามิเตอร์ที่สองคือสตริงรูปแบบทางกฎหมายที่สามารถแปลงเป็น std::string_view
หรือ std::wstring_view
และผลลัพธ์ของการแปลงคือนิพจน์คงที่และ Args ในคำถามนี้ เรากรอกแบบฟอร์มที่เราต้องการ ได้แก่ {}/{}
เราต้องการให้พารามิเตอร์สองตัวถูกยัดลงใน {}
เช่นเดียวกับที่เราใช้ printf(%d,x)
; พารามิเตอร์สองตัวสุดท้ายคือ "ค่าที่ต้องยัดลงใน {}
" นั่นคือพารามิเตอร์ ถูกจัดรูปแบบ
04
แก้ไขเทมเพลตคลาสที่กำหนดเพื่อให้มี ID ที่แตกต่างกันสำหรับอินสแตนซ์แต่ละประเภทที่แตกต่างกัน วันที่: 2023/7/25
ผู้ถาม: Adttil
# include < iostream >
class ComponentBase {
protected:
static inline std:: size_t component_type_count = 0 ;
};
template < typename T>
class Component : public ComponentBase {
public:
// todo...
//使用任意方式更改当前模板类,使得对于任意类型X,若其继承自Component
//则X::component_type_id()会得到一个独一无二的size_t类型的id(对于不同的X类型返回的值应不同)
//要求:不能使用std::type_info(禁用typeid关键字),所有id从0开始连续。
};
class A : public Component <A>
{};
class B : public Component <B>
{};
class C : public Component <C>
{};
int main ()
{
std::cout << A::component_type_id () << std::endl;
std::cout << B::component_type_id () << std::endl;
std::cout << B::component_type_id () << std::endl;
std::cout << A::component_type_id () << std::endl;
std::cout << A::component_type_id () << std::endl;
std::cout << C::component_type_id () << std::endl;
}
0
1
1
0
0
2
การส่งผลงานควรมีผลการทดสอบหลายแพลตฟอร์มดังแสดงในรูป:
template < typename T>
class Component : public ComponentBase {
public:
static std:: size_t component_type_id (){
static std:: size_t ID = component_type_count++;
return ID;
}
};
วิเคราะห์:
เราจำเป็นต้องใช้ฟังก์ชันสมาชิกแบบคงที่ component_type_id
ของ Component
สิ่งนี้ทราบจากรหัสที่ให้มา:
class A : public Component <A>
{};
A::component_type_id ()
คำถามต้องการให้ประเภทคลาสที่กำหนดเองแต่ละประเภท (ถือว่าเป็น X) สืบทอด Component<X>
และการเรียก component_type_id()
จะส่งคืน ID เฉพาะของตัวเอง เช่นเดียวกับประเภทอื่น ๆ
ก่อนที่จะแก้ไขปัญหา เราต้องเน้นประเด็นความรู้หนึ่งประเด็น:
เทมเพลต C++ ไม่ใช่ประเภทเฉพาะ แต่อยู่หลังการสร้างอินสแตนซ์ (นั่นคือ เทมเพลตฟังก์ชันไม่ใช่ฟังก์ชัน และเทมเพลตคลาสไม่ใช่คลาส ) สมาชิกแบบคงที่หรือฟังก์ชันสมาชิกแบบคงที่ของเทมเพลตคลาสไม่อยู่ในเทมเพลต แต่เป็น ประเภทเฉพาะหลังจากการสร้างอินสแตนซ์ เรา คุณสามารถใช้โค้ดเพื่อแสดงข้อสรุป:
# include < iostream >
template < typename T>
struct Test {
inline static int n = 10 ;
};
int main (){
Test< int >::n = 1 ;
std::cout << Test< void >::n << ' n ' ; // 10
std::cout << Test< int >::n << ' n ' ; // 1
}
รหัสนี้แสดงให้เห็นได้อย่างง่ายดายว่า สมาชิกข้อมูลคงที่อยู่ในประเภทเฉพาะหลังจากการสร้างอินสแตนซ์ของเทมเพลต Test<void>::n
และ Test<int>::n
ไม่เหมือนกัน n และ Test<void>
และ Test<int>
ไม่ใช่ประเภทเดียวกัน (เช่นเดียวกับฟังก์ชันสมาชิกแบบคงที่)
ดังนั้นโซลูชันของเราจึงใช้: เทมเพลตคลาสอินสแตนซ์ประเภทต่างๆ ก็มีฟังก์ชัน Component
แบบคงที่ที่แตกต่างกันเช่นกัน ส่วนแบบคงที่ในฟังก์ชันสมาชิกแบบคงที่นั้นไม่ซ้ำกันและจะเริ่มต้นได้ก็ต่อเมื่อถูกเรียกเป็นครั้งแรกเท่านั้น
05
ใช้ประเภท scope_guard
วันที่: 2023/7/29
ผู้ถาม: Da'Inihlus
จำเป็นต้องใช้ประเภท scope_guard
(นั่นคือ รองรับการส่งผ่านประเภทที่เรียกได้ใด ๆ และเรียกมันในเวลาเดียวกันระหว่างการทำลาย)
# include < cstdio >
# include < cassert >
# include < stdexcept >
# include < iostream >
# include < functional >
struct X {
X () { puts ( " X() " ); }
X ( const X&) { puts ( " X(const X&) " ); }
X (X&&) noexcept { puts ( " X(X&&) " ); }
~X () { puts ( " ~X() " ); }
};
int main () {
{
// scope_guard的作用之一,是让各种C风格指针接口作为局部变量时也能得到RAII支持
// 这也是本题的基础要求
FILE * fp = nullptr ;
try {
fp = fopen ( " test.txt " , " a " );
auto guard = scope_guard ([&] {
fclose (fp);
fp = nullptr ;
});
throw std::runtime_error{ " Test " };
} catch (std:: exception & e){
puts (e. what ());
}
assert (fp == nullptr );
}
puts ( " ---------- " );
{
// 附加要求1,支持函数对象调用
struct Test {
void operator ()(X* x) {
delete x;
}
} t;
auto x = new X{};
auto guard = scope_guard (t, x);
}
puts ( " ---------- " );
{
// 附加要求2,支持成员函数和std::ref
auto x = new X{};
{
struct Test {
void f (X*& px) {
delete px;
px = nullptr ;
}
} t;
auto guard = scope_guard{&Test::f, &t, std::ref (x)};
}
assert (x == nullptr );
}
}
Test
----------
X()
~X()
----------
X()
~X()
std::function
และลบประเภท struct scope_guard {
std::function< void ()>f;
template < typename Func, typename ...Args> requires std::invocable<Func, std:: unwrap_reference_t <Args>...>
scope_guard (Func&& func, Args&&...args) :f{ [func = std::forward<Func>(func), ... args = std::forward<Args>(args)]() mutable {
std::invoke (std::forward<std:: decay_t <Func>>(func), std:: unwrap_reference_t <Args>(std::forward<Args>(args))...);
} }{}
~scope_guard () { f (); }
scope_guard ( const scope_guard&) = delete ;
scope_guard& operator =( const scope_guard&) = delete ;
};
std::tuple
+ std::apply
template < typename F, typename ...Args>
requires requires (F f, Args...args) { std::invoke (f, args...); }
struct scope_guard {
F f;
std::tuple<Args...>values;
template < typename Fn, typename ...Ts>
scope_guard (Fn&& func, Ts&&...args) :f{ std::forward<Fn>(func) }, values{ std::forward<Ts>(args)... } {}
~scope_guard () {
std::apply (f, values);
}
scope_guard ( const scope_guard&) = delete ;
};
template < typename F, typename ...Args> //推导指引非常重要
scope_guard (F&&, Args&&...) -> scope_guard<std::decay_t<F>, std::decay_t<Args>...>;
06
ของการเริ่มต้น std::atomic
วันที่: 2023/8/2
ผู้ถาม: mq白
# include < iostream >
# include < atomic >
int main () {
std::atomic< int > n = 6 ;
std::cout << n << ' n ' ;
}
อธิบายว่าทำไมจึงสามารถคอมไพล์โค้ดข้างต้นได้หลังจาก C++17 แต่ไม่ใช่ก่อน C++17
ใน std::atomic<int> n = 6
เนื่องจาก 6
และ std::atomic<int>
ไม่ใช่ประเภทเดียวกัน (แต่จริงๆ แล้วมีลำดับการแปลงที่ผู้ใช้กำหนดที่นี่ คุณสามารถคิดได้ว่า 6
สามารถแปลงโดยปริยายได้ ).
นั่นคือการเรียกตัวสร้างการแปลง:
constexpr atomic ( T desired ) noexcept ;
ตัวสร้างการแปลงยังใช้เป็นส่วนหนึ่งของลำดับการแปลงที่ผู้ใช้กำหนด
6
จะเรียกตัวสร้างการแปลงเพื่อสร้างวัตถุอะตอมมิกชั่วคราวเพื่อ เริ่มต้นโดยตรง n
นั่นคือ
std::atomic< int > n (std::atomic< int >( 6 ))
ในเวอร์ชัน ก่อน C++17 เป็นเรื่องปกติที่ตัวสร้างการคัดลอก/ย้ายควรถูกค้นหาและตรวจพบก่อนจึงจะสามารถคอมไพล์ได้หากตรงตามข้อกำหนด แต่: