Sigslot เป็นการใช้งานช่องสัญญาณสำหรับ C++ แบบส่วนหัวเท่านั้น ปลอดภัยสำหรับเธรด
เป้าหมายหลักคือการแทนที่ Boost.Signals2
นอกเหนือจากคุณสมบัติตามปกติแล้วยังมีให้อีกด้วย
Sigslot ได้รับการทดสอบหน่วยแล้ว และควรจะเชื่อถือได้และมีเสถียรภาพเพียงพอที่จะแทนที่ Boost Signals2
การทดสอบดำเนินไปอย่างเรียบร้อยภายใต้ที่อยู่ เธรด และเครื่องมือกำจัดพฤติกรรมที่ไม่ได้กำหนดไว้
การใช้งานหลายอย่างอนุญาตให้มีประเภทการส่งคืนสัญญาณ แต่ Sigslot ไม่อนุญาตเพราะฉันไม่มีประโยชน์ หากฉันมั่นใจได้เป็นอย่างอื่น ฉันอาจเปลี่ยนใจในภายหลัง
ไม่จำเป็นต้องคอมไพล์หรือติดตั้ง เพียงรวม sigslot/signal.hpp
แล้วใช้งาน ปัจจุบัน Sigslot ขึ้นอยู่กับคอมไพเลอร์ที่สอดคล้องกับ C++14 แต่หากจำเป็น ก็สามารถดัดแปลงเป็น C++11 ได้ เป็นที่ทราบกันว่าสามารถทำงานร่วมกับคอมไพเลอร์ Clang 4.0 และ GCC 5.0+ บน GNU Linux, MSVC 2017 ขึ้นไป, Clang-cl และ MinGW บน Windows
อย่างไรก็ตาม โปรดระวัง gotcha ที่อาจเกิดขึ้นบน Windows ด้วยคอมไพเลอร์ MSVC และ Clang-Cl ซึ่งอาจต้องใช้แฟล็กเกอร์ลิงก์ /OPT:NOICF
ในสถานการณ์พิเศษ อ่านบทรายละเอียดการใช้งานเพื่อดูคำอธิบาย
ไฟล์รายการ CMake จัดทำขึ้นเพื่อวัตถุประสงค์ในการติดตั้งและสร้างโมดูลนำเข้า CMake นี่เป็นวิธีการติดตั้งที่แนะนำ เป้าหมายที่นำเข้า Pal::Sigslot
พร้อมใช้งานและใช้แฟล็กตัวเชื่อมโยงที่จำเป็นแล้ว นอกจากนี้ยังจำเป็นสำหรับตัวอย่างและการทดสอบ ซึ่งเป็นทางเลือกขึ้นอยู่กับการทดสอบหน่วย Qt5 และ Boost สำหรับอะแดปเตอร์
# Using Sigslot from cmake
find_package (PalSigslot)
add_executable (MyExe main.cpp)
target_link_libraries (MyExe PRIVATE Pal::Sigslot)
ตัวเลือกการกำหนดค่า SIGSLOT_REDUCE_COMPILE_TIME
พร้อมใช้งานในเวลากำหนดค่า เมื่อเปิดใช้งาน ระบบจะพยายามลดการขยายโค้ดโดยหลีกเลี่ยงการสร้างอินสแตนซ์เทมเพลตจำนวนมากที่เกิดจากการเรียกไปยัง std::make_shared
ตัวเลือกนี้ปิดอยู่ตามค่าเริ่มต้น แต่สามารถเปิดใช้งานได้สำหรับผู้ที่ต้องการเพิ่มขนาดโค้ดและเวลาในการคอมไพล์โดยใช้โค้ดที่มีประสิทธิภาพน้อยกว่าเล็กน้อย
การติดตั้งอาจทำได้โดยใช้คำแนะนำต่อไปนี้จากไดเร็กทอรีราก:
mkdir build && cd build
cmake .. -DSIGSLOT_REDUCE_COMPILE_TIME=ON -DCMAKE_INSTALL_PREFIX= ~ /local
cmake --build . --target install
# If you want to compile examples:
cmake --build . --target sigslot-examples
# And compile/execute unit tests:
cmake --build . --target sigslot-tests
Pal::Sigslot
สามารถรวมเข้าด้วยกันโดยใช้วิธี FetchContent
include (FetchContent)
FetchContent_Declare(
sigslot
GIT_REPOSITORY https://github.com/palacaze/sigslot
GIT_TAG 19a6f0f5ea11fc121fe67f81fd5e491f2d7a4637 # v1.2.0
)
FetchContent_MakeAvailable(sigslot)
add_executable (MyExe main.cpp)
target_link_libraries (MyExe PRIVATE Pal::Sigslot)
Sigslot ใช้โครงสร้างช่องสัญญาณที่ได้รับความนิยมในกรอบงาน UI ทำให้ง่ายต่อการใช้รูปแบบผู้สังเกตการณ์หรือการเขียนโปรแกรมตามเหตุการณ์ จุดเริ่มต้นหลักของไลบรารีคือเทมเพลตคลาส sigslot::signal<T...>
สัญญาณคืออ็อบเจ็กต์ที่สามารถส่งการแจ้งเตือนแบบพิมพ์ โดยค่าจริงจะถูกกำหนดไว้หลังพารามิเตอร์เทมเพลตคลาสสัญญาณ และลงทะเบียนตัวจัดการการแจ้งเตือน (ตัวเรียกได้) จำนวนเท่าใดก็ได้ของประเภทอาร์กิวเมนต์ที่เข้ากันได้ เพื่อดำเนินการด้วยค่าที่ให้มาทุกครั้งที่มีการปล่อยสัญญาณเกิดขึ้น ในสำนวนช่องสัญญาณ สิ่งนี้เรียกว่าการเชื่อมต่อช่องสัญญาณกับสัญญาณ โดยที่ "ช่อง" แสดงถึงอินสแตนซ์ที่สามารถเรียกได้ และ "การเชื่อมต่อ" ถือได้ว่าเป็นการเชื่อมโยงแนวคิดจากสัญญาณหนึ่งไปยังอีกช่องหนึ่ง
ตัวอย่างทั้งหมดที่นำเสนอด้านล่างนี้มีอยู่ในรูปแบบซอร์สโค้ดที่คอมไพล์ได้ในไดเร็กทอรีย่อยตัวอย่าง
นี่คือตัวอย่างแรกที่นำเสนอคุณลักษณะพื้นฐานที่สุดของไลบรารี
ก่อนอื่นเราประกาศ sig
สัญญาณที่ไม่มีพารามิเตอร์ จากนั้นเราจะเชื่อมต่อหลายช่องและสุดท้ายจะปล่อยสัญญาณที่ทริกเกอร์การเรียกใช้ทุกช่องที่สามารถเรียกได้เชื่อมต่อล่วงหน้า สังเกตว่าห้องสมุดจัดการกับรูปแบบการโทรที่หลากหลายได้อย่างไร
# include < sigslot/signal.hpp >
# include < iostream >
void f () { std::cout << " free function n " ; }
struct s {
void m () { std::cout << " member function n " ; }
static void sm () { std::cout << " static member function n " ; }
};
struct o {
void operator ()() { std::cout << " function object n " ; }
};
int main () {
s d;
auto lambda = []() { std::cout << " lambda n " ; };
auto gen_lambda = []( auto && ... a ) { std::cout << " generic lambda n " ; };
// declare a signal instance with no arguments
sigslot:: signal <> sig;
// connect slots
sig. connect (f);
sig. connect (&s::m, &d);
sig. connect (&s::sm);
sig. connect ( o ());
sig. connect (lambda);
sig. connect (gen_lambda);
// a free connect() function is also available
sigslot::connect (sig, f);
// emit a signal
sig ();
}
ตามค่าเริ่มต้น ลำดับการเรียกใช้สล็อตเมื่อส่งสัญญาณนั้นไม่ได้ระบุ โปรดอย่าวางใจว่าลำดับการเรียกใช้สล็อตจะเหมือนกันเสมอไป คุณสามารถจำกัดลำดับการเรียกใช้เฉพาะได้โดยใช้กลุ่มสล็อต ซึ่งจะนำเสนอในภายหลัง
ตัวอย่างแรกนั้นเรียบง่ายแต่ไม่ค่อยมีประโยชน์ ให้เรามาดูสัญญาณที่ปล่อยค่าแทน สัญญาณสามารถปล่อยอาร์กิวเมนต์จำนวนเท่าใดก็ได้ตามด้านล่าง
# include < sigslot/signal.hpp >
# include < iostream >
# include < string >
struct foo {
// Notice how we accept a double as first argument here.
// This is fine because float is convertible to double.
// 's' is a reference and can thus be modified.
void bar ( double d, int i, bool b, std::string &s) {
s = b ? std::to_string (i) : std::to_string (d);
}
};
// Function objects can cope with default arguments and overloading.
// It does not work with static and member functions.
struct obj {
void operator ()( float , int , bool , std::string &, int = 0 ) {
std::cout << " I was here n " ;
}
void operator ()() {}
};
int main () {
// declare a signal with float, int, bool and string& arguments
sigslot:: signal < float , int , bool , std::string&> sig;
// a generic lambda that prints its arguments to stdout
auto printer = [] ( auto a, auto && ... args ) {
std::cout << a;
( void )std::initializer_list< int >{
(( void )(std::cout << " " << args), 1 )...
};
std::cout << " n " ;
};
// connect the slots
foo ff;
sig. connect (printer);
sig. connect (&foo::bar, &ff);
sig. connect ( obj ());
float f = 1 . f ;
short i = 2 ; // convertible to int
std::string s = " 0 " ;
// emit a signal
sig (f, i, false , s);
sig (f, i, true , s);
}
ดังที่แสดงไว้ ประเภทอาร์กิวเมนต์ของสล็อตไม่จำเป็นต้องเหมือนกันทุกประการกับพารามิเตอร์เทมเพลตสัญญาณอย่างเคร่งครัด การแปลงจากนั้นเป็นเรื่องปกติ อาร์กิวเมนต์ทั่วไปก็ใช้ได้เช่นกัน ดังที่แสดงพร้อมกับแลมบ์ดาทั่วไป printer
(ซึ่งสามารถเขียนเป็นเทมเพลตฟังก์ชันได้เช่นกัน)
ขณะนี้มีข้อ จำกัด สองประการที่ฉันนึกถึงเกี่ยวกับการจัดการที่สามารถเรียกได้: อาร์กิวเมนต์เริ่มต้นและฟังก์ชันโอเวอร์โหลด ทั้งสองทำงานอย่างถูกต้องในกรณีของฟังก์ชันออบเจ็กต์ แต่จะล้มเหลวในการคอมไพล์ด้วยฟังก์ชันคงที่และสมาชิก ด้วยเหตุผลที่แตกต่างกันแต่เกี่ยวข้องกัน
พิจารณาโค้ดต่อไปนี้:
struct foo {
void bar ( double d);
void bar ();
};
&foo::bar
ควรอ้างอิงถึงอะไร? จากการโอเวอร์โหลด ตัวชี้เหนือฟังก์ชันสมาชิกนี้ไม่ได้แมปกับสัญลักษณ์เฉพาะ ดังนั้นคอมไพลเลอร์จึงไม่สามารถเลือกสัญลักษณ์ที่ถูกต้องได้ วิธีหนึ่งในการแก้ไขสัญลักษณ์ที่ถูกต้องคือการส่งตัวชี้ฟังก์ชันไปยังประเภทฟังก์ชันที่ถูกต้องอย่างชัดเจน นี่คือตัวอย่างที่ใช้เครื่องมือช่วยเหลือเล็กๆ น้อยๆ เพื่อให้มีไวยากรณ์ที่เบากว่า (อันที่จริง ฉันอาจจะเพิ่มสิ่งนี้ลงในไลบรารีเร็วๆ นี้)
# include < sigslot/signal.hpp >
template < typename ... Args, typename C>
constexpr auto overload ( void (C::*ptr)(Args...)) {
return ptr;
}
template < typename ... Args>
constexpr auto overload ( void (*ptr)(Args...)) {
return ptr;
}
struct obj {
void operator ()( int ) const {}
void operator ()() {}
};
struct foo {
void bar ( int ) {}
void bar () {}
static void baz ( int ) {}
static void baz () {}
};
void moo ( int ) {}
void moo () {}
int main () {
sigslot:: signal < int > sig;
// connect the slots, casting to the right overload if necessary
foo ff;
sig. connect (overload< int >(&foo::bar), &ff);
sig. connect (overload< int >(&foo::baz));
sig. connect (overload< int >(&moo));
sig. connect ( obj ());
sig ( 0 );
return 0 ;
}
อาร์กิวเมนต์เริ่มต้นไม่ได้เป็นส่วนหนึ่งของลายเซ็นประเภทฟังก์ชัน และสามารถกำหนดใหม่ได้ ดังนั้นจึงเป็นเรื่องยากที่จะจัดการ เมื่อเชื่อมต่อช่องเข้ากับสัญญาณ ไลบรารีจะพิจารณาว่าสามารถเรียกใช้ callable ที่ระบุด้วยประเภทอาร์กิวเมนต์ของสัญญาณได้หรือไม่ แต่ ณ จุดนี้ไม่ทราบถึงการมีอยู่ของอาร์กิวเมนต์ฟังก์ชันเริ่มต้น ดังนั้นจำนวนอาร์กิวเมนต์จึงไม่ตรงกัน
วิธีแก้ไขง่ายๆ สำหรับกรณีการใช้งานนี้คือการสร้าง bind adapter จริงๆ แล้วเราสามารถทำให้มันค่อนข้างทั่วไปได้ เช่น:
# include < sigslot/signal.hpp >
# define ADAPT ( func )
[=]( auto && ...a) { (func)(std::forward< decltype (a)>(a)...); }
void foo ( int &i, int b = 1 ) {
i += b;
}
int main () {
int i = 0 ;
// fine, all the arguments are handled
sigslot:: signal < int &, int > sig1;
sig1. connect (foo);
sig1 (i, 2 );
// must wrap in an adapter
i = 0 ;
sigslot:: signal < int &> sig2;
sig2. connect ( ADAPT (foo));
sig2 (i);
return 0 ;
}
สิ่งที่ไม่ชัดเจนจนถึงขณะนี้คือ signal::connect()
จริง ๆ แล้วส่งคืนออบ sigslot::connection
ที่อาจใช้เพื่อจัดการพฤติกรรมและอายุการใช้งานของการเชื่อมต่อช่องสัญญาณ sigslot::connection
เป็นวัตถุน้ำหนักเบา (โดยทั่วไป std::weak_ptr
) ที่อนุญาตให้โต้ตอบกับการเชื่อมต่อช่องสัญญาณที่กำลังดำเนินอยู่และเปิดเผยคุณสมบัติต่อไปนี้:
signal::connect()
sigslot::connection
ไม่ได้ผูกการเชื่อมต่อกับขอบเขต: นี่ไม่ใช่วัตถุ RAII ซึ่งอธิบายว่าทำไมจึงสามารถคัดลอกได้ อย่างไรก็ตามสามารถแปลงเป็น sigslot::scoped_connection
โดยปริยายได้ ซึ่งจะทำลายการเชื่อมต่อเมื่ออยู่นอกขอบเขต
นี่คือตัวอย่างที่แสดงให้เห็นคุณลักษณะบางอย่างเหล่านี้:
# include < sigslot/signal.hpp >
# include < string >
int i = 0 ;
void f () { i += 1 ; }
int main () {
sigslot:: signal <> sig;
// keep a sigslot::connection object
auto c1 = sig. connect (f);
// disconnection
sig (); // i == 1
c1. disconnect ();
sig (); // i == 1
// scope based disconnection
{
sigslot::scoped_connection sc = sig. connect (f);
sig (); // i == 2
}
sig (); // i == 2;
// connection blocking
auto c2 = sig. connect (f);
sig (); // i == 3
c2. block ();
sig (); // i == 3
c2. unblock ();
sig (); // i == 4
}
Sigslot รองรับลายเซ็นสล็อตแบบขยายพร้อมการอ้างอิง sigslot::connection
เพิ่มเติมเป็นอาร์กิวเมนต์แรก ซึ่งอนุญาตให้มีการจัดการการเชื่อมต่อจากภายในสล็อต ลายเซ็นแบบขยายนี้สามารถเข้าถึงได้โดยใช้เมธอด connect_extended()
# include < sigslot/signal.hpp >
int main () {
int i = 0 ;
sigslot:: signal <> sig;
// extended connection
auto f = []( auto &con) {
i += 1 ; // do work
con. disconnect (); // then disconnects
};
sig. connect_extended (f);
sig (); // i == 1
sig (); // i == 1 because f was disconnected
}
ผู้ใช้จะต้องตรวจสอบให้แน่ใจว่าอายุการใช้งานของสล็อตนั้นเกินกว่าสัญญาณหนึ่งซึ่งอาจน่าเบื่อในซอฟต์แวร์ที่ซับซ้อน เพื่อให้งานนี้ง่ายขึ้น Sigslot สามารถยกเลิกการเชื่อมต่อออบเจ็กต์สล็อตที่สามารถติดตามอายุการใช้งานได้โดยอัตโนมัติ ในการทำเช่นนั้น ช่องจะต้องแปลงเป็นตัวชี้จุดอ่อนในบางรูปแบบได้
std::shared_ptr
และ std::weak_ptr
ได้รับการสนับสนุนตั้งแต่แกะกล่อง และมีอะแดปเตอร์เพื่อรองรับ boost::shared_ptr
, boost::weak_ptr
และ Qt QSharedPointer
, QWeakPointer
และคลาสใดๆ ที่มาจาก QObject
สามารถเพิ่มออบเจ็กต์ที่ติดตามได้อื่นๆ ได้โดยการประกาศฟังก์ชันอะแดปเตอร์ to_weak()
# include < sigslot/signal.hpp >
# include < sigslot/adapter/qt.hpp >
int sum = 0 ;
struct s {
void f ( int i) { sum += i; }
};
class MyObject : public QObject {
Q_OBJECT
public:
void add ( int i) const { sum += i; }
};
int main () {
sum = 0 ;
signal < int > sig;
// track lifetime of object and also connect to a member function
auto p = std::make_shared<s>();
sig. connect (&s::f, p);
sig ( 1 ); // sum == 1
p. reset ();
sig ( 1 ); // sum == 1
// track an unrelated object lifetime
struct dummy ;
auto l = [&]( int i) { sum += i; };
auto d = std::make_shared<dummy>();
sig. connect (l, d);
sig ( 1 ); // sum == 2
d. reset ();
sig ( 1 ); // sum == 2
// track a QObject
{
MyObject o;
sig. connect (&MyObject::add, &o);
sig ( 1 ); // sum == 3
}
sig ( 1 ); // sum == 3
}
อีกวิธีหนึ่งในการตรวจสอบให้แน่ใจว่าตัวชี้ตัดการเชื่อมต่อสล็อตฟังก์ชันสมาชิกโดยอัตโนมัติคือการสืบทอดจาก sigslot::observer
หรือ sigslot::observer_st
อย่างชัดเจน แบบแรกเป็นแบบปลอดภัยสำหรับเธรด ตรงกันข้ามกับแบบหลัง
นี่คือตัวอย่างการใช้งาน
# include < sigslot/signal.hpp >
int sum = 0 ;
struct s : sigslot::observer_st {
void f ( int i) { sum += i; }
};
struct s_mt : sigslot::observer {
~s_mt () {
// Needed to ensure proper disconnection prior to object destruction
// in multithreaded contexts.
this -> disconnect_all ();
}
void f ( int i) { sum += i; }
};
int main () {
sum = 0 ;
signal < int > sig;
{
// Lifetime of object instance p is tracked
s p;
s_mt pm;
sig. connect (&s::f, &p);
sig. connect (&s_mt::f, &pm);
sig ( 1 ); // sum == 2
}
// The slots got disconnected at instance destruction
sig ( 1 ); // sum == 2
}
วัตถุที่ใช้วิธีการล่วงล้ำนี้อาจเชื่อมต่อกับสัญญาณที่ไม่เกี่ยวข้องจำนวนเท่าใดก็ได้
รองรับการตัดการเชื่อมต่อสล็อตโดยการจัดหาลายเซ็นฟังก์ชันที่เหมาะสม ตัวชี้วัตถุ หรือตัวติดตาม ได้รับการแนะนำในเวอร์ชัน 1.2.0
คุณสามารถตัดการเชื่อมต่อช่องจำนวนเท่าใดก็ได้โดยใช้ signal::disconnect()
ซึ่งเสนอการโอเวอร์โหลด 4 ครั้งเพื่อระบุเกณฑ์การตัดการเชื่อมต่อ:
การตัดการเชื่อมต่อของแลมบ์ดาทำได้เฉพาะกับแลมบ์ดาที่ผูกไว้กับตัวแปรเท่านั้น เนื่องจากมีลักษณะเฉพาะ
โอเวอร์โหลดครั้งที่สองในปัจจุบันต้องการ RTTI เพื่อตัดการเชื่อมต่อจากพอยน์เตอร์ไปยังฟังก์ชันสมาชิก ฟังก์ชันออบเจ็กต์ และแลมบ์ดา ข้อจำกัดนี้ใช้ไม่ได้กับฟังก์ชันสมาชิกแบบฟรีและแบบคงที่ สาเหตุมาจากข้อเท็จจริงที่ว่าในภาษา C++ ตัวชี้ไปยังฟังก์ชันสมาชิกของประเภทที่ไม่เกี่ยวข้องกันนั้นไม่สามารถเทียบเคียงได้ ซึ่งตรงกันข้ามกับตัวชี้ไปยังฟังก์ชันสมาชิกแบบคงที่และอิสระ ตัวอย่างเช่น ตัวชี้ไปยังฟังก์ชันสมาชิกของเมธอดเสมือนของคลาสที่แตกต่างกันสามารถมีที่อยู่เดียวกันได้ (พวกมันจะเก็บออฟเซ็ตของเมธอดไว้ใน vtable)
อย่างไรก็ตาม สามารถคอมไพล์ Sigslot โดยปิดใช้งาน RTTI และการโอเวอร์โหลดจะถูกปิดใช้งานในกรณีที่มีปัญหา
ในฐานะโหนดด้านข้าง ฟีเจอร์นี้เป็นที่ยอมรับว่ามีการเพิ่มโค้ดมากกว่าที่คาดไว้ในตอนแรก เนื่องจากเป็นเรื่องยากและผิดพลาดได้ง่าย ได้รับการออกแบบมาอย่างรอบคอบ โดยคำนึงถึงความถูกต้อง และไม่มีค่าใช้จ่ายแอบแฝงใดๆ เว้นแต่คุณจะใช้งานจริง
นี่คือตัวอย่างที่สาธิตคุณลักษณะนี้
# include < sigslot/signal.hpp >
# include < string >
static int i = 0 ;
void f1 () { i += 1 ; }
void f2 () { i += 1 ; }
struct s {
void m1 () { i += 1 ; }
void m2 () { i += 1 ; }
void m3 () { i += 1 ; }
};
struct o {
void operator ()() { i += 1 ; }
};
int main () {
sigslot:: signal <> sig;
s s1;
auto s2 = std::make_shared<s>();
auto lbd = [&] { i += 1 ; };
sig. connect (f1); // #1
sig. connect (f2); // #2
sig. connect (&s::m1, &s1); // #3
sig. connect (&s::m2, &s1); // #4
sig. connect (&s::m3, &s1); // #5
sig. connect (&s::m1, s2); // #6
sig. connect (&s::m2, s2); // #7
sig. connect (o{}); // #8
sig. connect (lbd); // #9
sig (); // i == 9
sig. disconnect (f2); // #2 is removed
sig. disconnect (&s::m1); // #3 and #6 are removed
sig. disconnect (o{}); // #8 and is removed
// sig.disconnect(&o::operator()); // same as the above, more efficient
sig. disconnect (lbd); // #9 and is removed
sig. disconnect (s2); // #7 is removed
sig. disconnect (&s::m3, &s1); // #5 is removed, not #4
sig (); // i == 11
sig. disconnect_all (); // remove all remaining slots
return 0 ;
}
จากเวอร์ชัน 1.2.0 สามารถกำหนดช่องรหัสกลุ่มเพื่อควบคุมลำดับที่เกี่ยวข้องของการเรียกใช้ช่องได้
ลำดับการเรียกใช้สล็อตในกลุ่มเดียวกันไม่ได้ระบุและไม่ควรยึดถือ อย่างไรก็ตาม กลุ่มสล็อตจะถูกเรียกใช้โดยเรียงลำดับรหัสกลุ่มจากน้อยไปหามาก เมื่อไม่ได้ตั้งค่า ID กลุ่มของสล็อต จะมีการกำหนดให้กับกลุ่ม 0 รหัสกลุ่มสามารถมีค่าใดๆ ในช่วงของจำนวนเต็ม 32 บิตที่มีเครื่องหมาย
# include < sigslot/signal.hpp >
# include < cstdio >
# include < limits >
int main () {
sigslot:: signal <> sig;
// simply assigning a group id as last argument to connect
sig. connect ([] { std::puts ( " Second " ); }, 1 );
sig. connect ([] { std::puts ( " Last " ); }, std::numeric_limits<sigslot::group_id>:: max ());
sig. connect ([] { std::puts ( " First " ); }, - 10 );
sig ();
return 0 ;
}
ฟังก์ชัน sigslot::connect()
แบบอิสระสามารถใช้เพื่อเชื่อมต่อสัญญาณไปยังอีกสัญญาณหนึ่งด้วยอาร์กิวเมนต์ที่เข้ากันได้
# include < sigslot/signal.hpp >
# include < iostream >
int main () {
sigslot:: signal < int > sig1;
sigslot:: signal < double > sig2;
sigslot::connect (sig1, sig2);
sigslot::connect (sig2, [] ( double d) { std::cout << " got " << d << std::endl; });
sig ( 1 );
return 0 ;
}
ความปลอดภัยของเกลียวได้รับการทดสอบแล้ว โดยเฉพาะอย่างยิ่ง การปล่อยสัญญาณข้ามและการปล่อยแบบเรียกซ้ำจะทำงานได้ดีในสถานการณ์ที่มีหลายเธรด
sigslot::signal
เป็น typedef สำหรับคลาสเทมเพลต sigslot::signal_base
ทั่วไป ซึ่งอาร์กิวเมนต์เทมเพลตแรกต้องเป็นประเภทที่ล็อคได้ ประเภทนี้จะกำหนดนโยบายการล็อกของชั้นเรียน
Sigslot มี 2 typedefs
sigslot::signal
ใช้งานได้จากหลายเธรดและใช้ std::mutex เป็นตัวล็อคได้ โดยเฉพาะอย่างยิ่ง การเชื่อมต่อ การตัดการเชื่อมต่อ การปล่อยสัญญาณ และการดำเนินการสล็อตนั้นปลอดภัยสำหรับเธรด อีกทั้งยังปลอดภัยด้วยการปล่อยสัญญาณแบบเรียกซ้ำsigslot::signal_st
เป็นทางเลือกที่ไม่ปลอดภัยสำหรับเธรด โดยแลกความปลอดภัยเพื่อการทำงานที่เร็วขึ้นเล็กน้อย การเปรียบเทียบตัวชี้ฟังก์ชันถือเป็นฝันร้ายใน C ++ นี่คือตารางที่แสดงขนาดและที่อยู่ของเคสต่างๆ เพื่อใช้เป็นตัวอย่าง:
void fun () {}
struct b1 {
virtual ~b1 () = default ;
static void sm () {}
void m () {}
virtual void vm () {}
};
struct b2 {
virtual ~b2 () = default ;
static void sm () {}
void m () {}
virtual void vm () {}
};
struct c {
virtual ~c () = default ;
virtual void w () {}
};
struct d : b1 {
static void sm () {}
void m () {}
void vm () override {}
};
struct e : b1, c {
static void sm () {}
void m () {}
void vm () override {}
};
เครื่องหมาย | GCC 9 ลินุกซ์ 64 ขนาดของ | GCC 9 ลินุกซ์ 64 ที่อยู่ | เอ็มเอสวีซี 16.6 32 ขนาดของ | เอ็มเอสวีซี 16.6 32 ที่อยู่ | GCC 8 หมิงหมิง 32 ขนาดของ | GCC 8 หมิงหมิง 32 ที่อยู่ | เสียงดังกราว-cl 9 32 ขนาดของ | เสียงดังกราว-cl 9 32 ที่อยู่ |
---|---|---|---|---|---|---|---|---|
สนุก | 8 | 0x802340 | 4 | 0x1311A6 | 4 | 0xF41540 | 4 | 0x0010AE |
&b1::สม | 8 | 0xE03140 | 4 | 0x7612A5 | 4 | 0x308D40 | 4 | 0x0010AE |
&b1::ม | 16 | 0xF03240 | 4 | 0x1514A5 | 8 | 0x248D40 | 4 | 0x0010AE |
&b1::vm | 16 | 0x11 | 4 | 0x9F11A5 | 8 | 0x09 | 4 | 0x8023AE |
&b2::สม | 8 | 0x003340 | 4 | 0xA515A5 | 4 | 0x408D40 | 4 | 0x0010AE |
&b2::ม | 16 | 0x103440 | 4 | 0xEB10A5 | 8 | 0x348D40 | 4 | 0x0010AE |
&b2::vm | 16 | 0x11 | 4 | 0x6A14A5 | 8 | 0x09 | 4 | 0x8023AE |
&d::สม | 8 | 0x203440 | 4 | 0x2612A5 | 4 | 0x108D40 | 4 | 0x0010AE |
&d::ม | 16 | 0x303540 | 4 | 0x9D13A5 | 8 | 0x048D40 | 4 | 0x0010AE |
&d::vm | 16 | 0x11 | 4 | 0x4412A5 | 8 | 0x09 | 4 | 0x8023AE |
&e::สม | 8 | 0x403540 | 4 | 0xF911A5 | 4 | 0x208D40 | 4 | 0x0010AE |
&e::ม | 16 | 0x503640 | 8 | 0x8111A5 | 8 | 0x148D40 | 8 | 0x0010AE |
&e::vm | 16 | 0x11 | 8 | 0xA911A5 | 8 | 0x09 | 8 | 0x8023AE |
MSVC และ Clang-cl ในโหมด Release ปรับฟังก์ชันให้เหมาะสมด้วยคำจำกัดความเดียวกันโดยการรวมเข้าด้วยกัน นี่เป็นลักษณะการทำงานที่สามารถปิดใช้งานได้ด้วยตัวเลือกตัวเชื่อมโยง /OPT:NOICF
การทดสอบและตัวอย่าง Sigslot อาศัยการเรียกที่เหมือนกันจำนวนมากซึ่งทริกเกอร์พฤติกรรมนี้ ซึ่งเป็นเหตุผลว่าทำไมจึงปิดใช้งานการปรับให้เหมาะสมเฉพาะนี้บนคอมไพเลอร์ที่ได้รับผลกระทบ
การใช้ lambdas ทั่วไปที่มี GCC น้อยกว่าเวอร์ชัน 7.4 สามารถทริกเกอร์ Bug #68071 ได้