├── examples ├── hello_world.cpp ├── connect_member_function.cpp ├── disconnect_from_connection_owner.cpp ├── subject_blocker.cpp ├── inherit_from_owner.cpp ├── connect_function_objects.cpp ├── variable_argument_count.cpp └── chain_subjects.cpp ├── LICENSE ├── makefile ├── CHANGELOG.txt ├── vs2019 ├── Observer.vcxproj ├── hello_world.vcxproj ├── chain_subjects.vcxproj ├── subject_blocker.vcxproj ├── inherit_from_owner.vcxproj ├── connect_function_objects.vcxproj ├── connect_member_function.vcxproj ├── variable_argument_count.vcxproj ├── disconnect_from_connection_owner.vcxproj ├── benchmark.vcxproj ├── tests.vcxproj └── Observer.sln ├── benchmark └── benchmark.cpp ├── README.md ├── src └── observer.h └── test └── tests.cpp /examples/hello_world.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void hello_world() 5 | { 6 | std::cout << "Hello World!" << std::endl; 7 | } 8 | 9 | int main( int /* argc */, char * /* argv */[] ) 10 | { 11 | pg::subject<> s; 12 | 13 | auto connection = pg::connect( s, hello_world ); 14 | 15 | s.notify(); 16 | 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /examples/connect_member_function.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct foo 5 | { 6 | void bar() 7 | { 8 | std::cout << "Hello World!" << std::endl; 9 | } 10 | }; 11 | 12 | int main( int /* argc */, char * /* argv */[] ) 13 | { 14 | pg::subject<> s; 15 | foo f; 16 | 17 | auto connection = pg::connect( s, &f, &foo::bar ); 18 | 19 | s.notify(); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /examples/disconnect_from_connection_owner.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main( int /* argc */, char * /* argv */[] ) 5 | { 6 | pg::connection_owner owner; 7 | pg::subject<> s; 8 | 9 | const auto handle = owner.connect( s, []{ std::cout << "Hello World!" << std::endl; } ); 10 | 11 | s.notify(); // Prints "Hello World!" 12 | 13 | owner.disconnect( handle ); 14 | 15 | s.notify(); // Prints nothing 16 | 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /examples/subject_blocker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main( int /* argc */, char * /* argv */[] ) 5 | { 6 | pg::blockable_subject< const char* > s; 7 | 8 | auto connection = pg::connect( s, []( const char* const msg ){ std::cout << msg << std::endl; } ); 9 | 10 | s.notify( "Hello World!" ); 11 | 12 | { 13 | pg::subject_blocker< pg::blockable_subject< const char* > > blocker( s ); 14 | s.notify( "Blocked!" ); 15 | } 16 | 17 | s.notify( "Hello World again!" ); 18 | 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /examples/inherit_from_owner.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct bar : public pg::connection_owner 5 | { 6 | bar( pg::subject< std::string >& foo ) 7 | { 8 | connect( foo, this, &bar::print ); 9 | } 10 | 11 | void print( const std::string &str ) 12 | { 13 | std::cout << str << std::endl; 14 | } 15 | }; 16 | 17 | int main( int /* argc */, char * /* argv */[] ) 18 | { 19 | pg::subject< std::string > foo; 20 | bar b( foo ); 21 | 22 | foo.notify( "Hello World!" ); 23 | 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /examples/connect_function_objects.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct functor 6 | { 7 | void operator()() 8 | { 9 | std::cout << "Hello World!" << std::endl; 10 | } 11 | }; 12 | 13 | int main( int /* argc */, char * /* argv */[] ) 14 | { 15 | pg::subject<> s; 16 | 17 | const std::function< void() > function = []{ std::cout << "Hello PG1003!" << std::endl; }; 18 | 19 | // NOTE: 'functor' and 'function' are passed by value. 20 | // This means they are copied into the observer 21 | auto connection_1 = pg::connect( s, functor() ); 22 | auto connection_2 = pg::connect( s, function ); 23 | 24 | s.notify(); 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /examples/variable_argument_count.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main( int /* argc */, char * /* argv */[] ) 5 | { 6 | pg::connection_owner owner; 7 | pg::subject< std::string, int > s; 8 | 9 | // All values from subject 10 | owner.connect( s, []( const std::string &str, int i ) 11 | { 12 | std::cout << "Hello " << str << i << "!" << std::endl; 13 | } ); 14 | 15 | // Only first value from subject 16 | owner.connect( s, []( const std::string &str ) 17 | { 18 | std::cout << "Hello " << str << "!" << std::endl; 19 | } ); 20 | 21 | // No value 22 | owner.connect( s, [] 23 | { 24 | std::cout << "Hello!" << std::endl; 25 | } ); 26 | 27 | s.notify( "PG", 1003 ); 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /examples/chain_subjects.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main( int /* argc */, char * /* argv */[] ) 5 | { 6 | pg::connection_owner owner; 7 | pg::subject<> s1; 8 | pg::subject< const char* > s2; 9 | pg::subject<> sy; 10 | pg::subject<> s; 11 | 12 | // Connect sy both to s1 and s2 13 | owner.connect( s1, &sy, &pg::subject<>::notify ); 14 | owner.connect( s2, &sy, &pg::subject<>::notify ); 15 | 16 | // Redirect notifications from sy to s 17 | owner.connect( sy, &s, &pg::subject<>::notify ); 18 | 19 | owner.connect( s, []{ std::cout << "Hello World!" << std::endl; } ); 20 | 21 | // Print "Hello World!" two times. 22 | s1.notify(); 23 | s2.notify( "PG1003" ); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 PG1003 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CXXFLAGS = -std=c++17 -Wall -Wextra -Wpedantic -O3 3 | INCLUDES = -I "./src" 4 | LDFLAGS = 5 | 6 | EXAMPLEDIR = examples 7 | TESTDIR = test 8 | BENCHMARKDIR = benchmark 9 | OBJDIR = obj 10 | OUTDIR = out 11 | 12 | TESTSOURCES = $(shell find $(TESTDIR) -type f -name '*.cpp') 13 | TESTOBJECTS = $(patsubst $(TESTDIR)/%.cpp, $(OBJDIR)/%.o, $(TESTSOURCES)) 14 | TESTS = $(patsubst $(OBJDIR)/%.o, $(OUTDIR)/%, $(TESTOBJECTS)) 15 | 16 | EXAMPLESOURCES = $(shell find $(EXAMPLEDIR) -type f -name '*.cpp') 17 | EXAMPLEOBJECTS = $(patsubst $(EXAMPLEDIR)/%.cpp, $(OBJDIR)/%.o, $(EXAMPLESOURCES)) 18 | EXAMPLES = $(patsubst $(OBJDIR)/%.o, $(OUTDIR)/%, $(EXAMPLEOBJECTS)) 19 | 20 | BENCHMARKSOURCES = $(shell find $(BENCHMARKDIR) -type f -name '*.cpp') 21 | BENCHMARKOBJECTS = $(patsubst $(BENCHMARKDIR)/%.cpp, $(OBJDIR)/%.o, $(BENCHMARKSOURCES)) 22 | BENCHMARKS = $(patsubst $(OBJDIR)/%.o, $(OUTDIR)/%, $(BENCHMARKOBJECTS)) 23 | 24 | .phony: clean tests examples benchmarks 25 | 26 | $(OBJDIR): 27 | test ! -d $(OBJDIR) && mkdir $(OBJDIR) 28 | 29 | $(OUTDIR): 30 | test ! -d $(OUTDIR) && mkdir $(OUTDIR) 31 | 32 | all: tests examples benchmarks 33 | 34 | tests:$(OBJDIR) $(OUTDIR) $(TESTS) 35 | 36 | $(TESTS): $(TESTOBJECTS) 37 | $(CXX) $(LDFLAGS) -o $@ $(patsubst $(OUTDIR)%, $(OBJDIR)%.o, $@) 38 | 39 | $(TESTOBJECTS): $(TESTSOURCES) 40 | $(CXX) $(CXXFLAGS) $(INCLUDES) -c $(patsubst $(OBJDIR)%.o, $(TESTDIR)%.cpp, $@) -o $@ 41 | 42 | examples:$(OBJDIR) $(OUTDIR) $(EXAMPLES) 43 | 44 | $(EXAMPLES): $(EXAMPLEOBJECTS) 45 | $(CXX) $(LDFLAGS) -o $@ $(patsubst $(OUTDIR)%, $(OBJDIR)%.o, $@) 46 | 47 | $(EXAMPLEOBJECTS): $(EXAMPLESOURCES) 48 | $(CXX) $(CXXFLAGS) $(INCLUDES) -c $(patsubst $(OBJDIR)%.o, $(EXAMPLEDIR)%.cpp, $@) -o $@ 49 | 50 | benchmarks:$(OBJDIR) $(OUTDIR) $(BENCHMARKS) 51 | 52 | $(BENCHMARKS): $(BENCHMARKOBJECTS) 53 | $(CXX) $(LDFLAGS) -o $@ $(patsubst $(OUTDIR)%, $(OBJDIR)%.o, $@) 54 | 55 | $(BENCHMARKOBJECTS): $(BENCHMARKSOURCES) 56 | $(CXX) $(CXXFLAGS) $(INCLUDES) -c $(patsubst $(OBJDIR)%.o, $(BENCHMARKDIR)%.cpp, $@) -o $@ 57 | 58 | clean: 59 | rm -rf $(OBJDIR) 60 | rm -rf $(OUTDIR) 61 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | # 2.x.y 2 | 3 | Changes: 4 | - Added [[nodiscard]] attribute for pg::connect functions. 5 | - Fixed level 4 warning C4840 when building the tests as C++17 with MSVC. 6 | - Added [[deprecated]] attribute for the pg::observer_owner. 7 | 8 | # 2.1.0 9 | 10 | Changes: 11 | - Removed apex_observer class from the public API and moved it to the detail 12 | namespace of the observer library. 13 | - Fixed compilation with C++17 or newer. 14 | - Build tests, examples and benchmark with C++17. 15 | 16 | # 2.0.2 17 | 18 | Changes: 19 | - Revisited noexcept specifiers. 20 | - Fixed a universal reference for the pg::connection_owner::connect overload 21 | that connects function objects. 22 | - Fixed connecting const member functions for instances that are const. 23 | 24 | # 2.0.1 25 | 26 | Changes: 27 | - pg::connection_owner now uses a vector instead of a set to store connections. 28 | A vector is faster more memory efficient than a set when used as a storage. 29 | Also this makes possible to make the order of disconnecting observers from 30 | the subjects the reverse of they were connected. 31 | - Added pg::connect functions which return a pg::scoped_connection. These can 32 | be used in case a connection_owner is a overkill, for example when you need 33 | to connect only one subject. 34 | The lifetime of the connection is coupeld to pg::scoped_connection. 35 | - Renamed pg::observer_owner to pg::connection_owner and provided an alias for 36 | the old name to ensure backwards compatibility. 37 | The new name expresses better the functionality of the object. 38 | - pg::connection_owner supports static polymorphism for subjects. A subject is 39 | required to have a connect and a disconnect function that accepts an observer 40 | interface pointer as parameter. 41 | - Added pg::blockable_subject. This subject type maintains a block count so 42 | that multiple resources can block its notifications. 43 | 44 | Breaking changes: 45 | - The overload pg::connection_owner::connect which connected one subject to 46 | another is removed. Use the connect overload for member functions instead. 47 | See tests or chain_subjects example. 48 | - pg::subject is no longer blockable, use pg::blockable_subject instead. 49 | - pg::subject_blocker now requires subjects to provide a block and a unblock 50 | function, for example like pg::blockable_subject. 51 | - A connection is no longer automaticly invalidated. The usability of this 52 | feature didn't justify the extra complexity. 53 | 54 | # 1.1.1 55 | 56 | Changes: 57 | - Restored the detail namespace in observer.h. 58 | - Added a simple benchmark to get an idea about the overhead. 59 | 60 | # 1.1.0 61 | 62 | Changes: 63 | - The destructor of subject now calls the disconnect of its observers in the 64 | reversed order the in which observers were connected. 65 | - Refactored the connection handle. 66 | - Made pg::invoke part of the public API. 67 | - Added doxygen documentation for the public API. 68 | - Changed the observer interface to allow custom observer types. 69 | 70 | # v1.0.0 71 | 72 | - Initial release 73 | -------------------------------------------------------------------------------- /vs2019/Observer.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {06A03865-7E6C-4804-86E1-8E13A82B0C32} 24 | observer 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Platform)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Platform)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | 91 | 92 | Console 93 | 94 | 95 | 96 | 97 | Level3 98 | Disabled 99 | true 100 | true 101 | 102 | 103 | Console 104 | 105 | 106 | 107 | 108 | Level3 109 | MaxSpeed 110 | true 111 | true 112 | true 113 | true 114 | 115 | 116 | Console 117 | true 118 | true 119 | 120 | 121 | 122 | 123 | Level3 124 | MaxSpeed 125 | true 126 | true 127 | true 128 | true 129 | 130 | 131 | Console 132 | true 133 | true 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /vs2019/hello_world.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {196CD1D1-4B1F-427A-8712-99FEEC82D310} 24 | hello_world 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Platform)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Platform)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | ../src;%(AdditionalIncludeDirectories) 91 | 92 | 93 | Console 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | ../src;%(AdditionalIncludeDirectories) 103 | 104 | 105 | Console 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | ../src;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | true 132 | ../src;%(AdditionalIncludeDirectories) 133 | 134 | 135 | Console 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /vs2019/chain_subjects.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {CA073B63-C0E9-4D9F-A796-D40B783B053F} 24 | chain_subjects 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Platform)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Platform)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | ../src;%(AdditionalIncludeDirectories) 91 | 92 | 93 | Console 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | ../src;%(AdditionalIncludeDirectories) 103 | 104 | 105 | Console 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | ../src;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | true 132 | ../src;%(AdditionalIncludeDirectories) 133 | 134 | 135 | Console 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /vs2019/subject_blocker.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40} 24 | subject_blocker 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Platform)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Platform)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | ../src;%(AdditionalIncludeDirectories) 91 | 92 | 93 | Console 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | ../src;%(AdditionalIncludeDirectories) 103 | 104 | 105 | Console 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | ../src;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | true 132 | ../src;%(AdditionalIncludeDirectories) 133 | 134 | 135 | Console 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /vs2019/inherit_from_owner.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1} 24 | inherit_from_owner 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Platform)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Platform)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | ../src;%(AdditionalIncludeDirectories) 91 | 92 | 93 | Console 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | ../src;%(AdditionalIncludeDirectories) 103 | 104 | 105 | Console 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | ../src;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | true 132 | ../src;%(AdditionalIncludeDirectories) 133 | 134 | 135 | Console 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /vs2019/connect_function_objects.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {0FEBA0AE-205A-4094-9888-545F79EFD04F} 24 | connect_function_objects 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Platform)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Platform)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | ../src;%(AdditionalIncludeDirectories) 91 | 92 | 93 | Console 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | ../src;%(AdditionalIncludeDirectories) 103 | 104 | 105 | Console 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | ../src;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | true 132 | ../src;%(AdditionalIncludeDirectories) 133 | 134 | 135 | Console 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /vs2019/connect_member_function.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC} 24 | connect_member_function 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Platform)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Platform)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | ../src;%(AdditionalIncludeDirectories) 91 | 92 | 93 | Console 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | ../src;%(AdditionalIncludeDirectories) 103 | 104 | 105 | Console 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | ../src;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | true 132 | ../src;%(AdditionalIncludeDirectories) 133 | 134 | 135 | Console 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /vs2019/variable_argument_count.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952} 24 | variable_argument_count 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Platform)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Platform)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | ../src;%(AdditionalIncludeDirectories) 91 | 92 | 93 | Console 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | ../src;%(AdditionalIncludeDirectories) 103 | 104 | 105 | Console 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | ../src;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | true 132 | ../src;%(AdditionalIncludeDirectories) 133 | 134 | 135 | Console 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /vs2019/disconnect_from_connection_owner.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {57057C0D-6277-49FE-A0F6-78E898F238C5} 24 | disconnect_from_connection_owner 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)\$(Platform)\$(Configuration)\ 74 | 75 | 76 | $(ProjectName)\$(Platform)\$(Configuration)\ 77 | 78 | 79 | $(ProjectName)\$(Configuration)\ 80 | 81 | 82 | $(ProjectName)\$(Configuration)\ 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | ../src;%(AdditionalIncludeDirectories) 91 | 92 | 93 | Console 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | ../src;%(AdditionalIncludeDirectories) 103 | 104 | 105 | Console 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | ../src;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | MaxSpeed 128 | true 129 | true 130 | true 131 | true 132 | ../src;%(AdditionalIncludeDirectories) 133 | 134 | 135 | Console 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /vs2019/benchmark.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 16.0 26 | {933638EE-696B-41C9-BA51-7D72D236D3CE} 27 | Win32Proj 28 | benchmark 29 | 10.0 30 | 31 | 32 | 33 | Application 34 | true 35 | v142 36 | Unicode 37 | 38 | 39 | Application 40 | false 41 | v142 42 | true 43 | Unicode 44 | 45 | 46 | Application 47 | true 48 | v142 49 | Unicode 50 | 51 | 52 | Application 53 | false 54 | v142 55 | true 56 | Unicode 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | true 78 | $(ProjectName)\$(Platform)\$(Configuration)\ 79 | 80 | 81 | true 82 | 83 | 84 | false 85 | 86 | 87 | false 88 | $(ProjectName)\$(Platform)\$(Configuration)\ 89 | 90 | 91 | 92 | 93 | 94 | Level4 95 | true 96 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 97 | true 98 | ..\src;%(AdditionalIncludeDirectories) 99 | true 100 | stdcpp14 101 | 102 | 103 | Console 104 | true 105 | 106 | 107 | 108 | 109 | 110 | 111 | Level3 112 | true 113 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 114 | true 115 | 116 | 117 | Console 118 | true 119 | 120 | 121 | 122 | 123 | 124 | 125 | Level3 126 | true 127 | true 128 | true 129 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 130 | true 131 | 132 | 133 | Console 134 | true 135 | true 136 | true 137 | 138 | 139 | 140 | 141 | 142 | 143 | Level4 144 | true 145 | true 146 | true 147 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 148 | true 149 | ..\src;%(AdditionalIncludeDirectories) 150 | true 151 | stdcpp14 152 | 153 | 154 | Console 155 | true 156 | true 157 | true 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /vs2019/tests.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 16.0 26 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE} 27 | Win32Proj 28 | test 29 | 10.0 30 | tests 31 | 32 | 33 | 34 | Application 35 | true 36 | v142 37 | Unicode 38 | 39 | 40 | Application 41 | false 42 | v142 43 | true 44 | Unicode 45 | 46 | 47 | Application 48 | true 49 | Unicode 50 | v142 51 | 52 | 53 | Application 54 | false 55 | true 56 | Unicode 57 | v142 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | true 79 | $(ProjectName)\$(Platform)\$(Configuration)\ 80 | 81 | 82 | true 83 | 84 | 85 | false 86 | 87 | 88 | false 89 | $(ProjectName)\$(Platform)\$(Configuration)\ 90 | 91 | 92 | 93 | 94 | 95 | Level4 96 | true 97 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 98 | true 99 | ../src;%(AdditionalIncludeDirectories) 100 | true 101 | stdcpp14 102 | 103 | 104 | Console 105 | true 106 | 107 | 108 | 109 | 110 | 111 | 112 | Level3 113 | true 114 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 115 | true 116 | 117 | 118 | Console 119 | true 120 | 121 | 122 | 123 | 124 | 125 | 126 | Level3 127 | true 128 | true 129 | true 130 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 131 | true 132 | 133 | 134 | Console 135 | true 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | 144 | Level4 145 | true 146 | true 147 | true 148 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 149 | true 150 | ../src;%(AdditionalIncludeDirectories) 151 | true 152 | stdcpp14 153 | 154 | 155 | Console 156 | true 157 | true 158 | true 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /benchmark/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | using namespace std::chrono; 8 | 9 | volatile int count_value = 0; 10 | int iterations = 100000000; 11 | volatile int increment = 1; 12 | 13 | 14 | void increase_count( int value ) 15 | { 16 | count_value += value; 17 | } 18 | 19 | struct increase_functor 20 | { 21 | int m_value = 0; 22 | 23 | increase_functor( int value ) 24 | : m_value( value ) 25 | {} 26 | 27 | void operator()( int ) 28 | { 29 | count_value += m_value; 30 | } 31 | }; 32 | 33 | struct increase 34 | { 35 | int m_value = 0; 36 | 37 | increase( int value ) 38 | : m_value( value ) 39 | {} 40 | 41 | void increase_count( int ) 42 | { 43 | count_value += m_value; 44 | } 45 | }; 46 | 47 | int main( int /* argc */, char * /* argv */[] ) 48 | { 49 | pg::connection_owner owner; 50 | 51 | std::function< void( int ) > increase_count_std_function = [&]( int value ){ count_value += value; }; 52 | auto increase_count_lambda_function = [&]( int value ){ count_value += value; }; 53 | auto increase_count_functor = increase_functor( increment ); 54 | auto increase_member_function = increase( increment ); 55 | 56 | // Get the baseline 57 | count_value = 0; 58 | const auto baseline_start_free_function = std::chrono::steady_clock::now(); 59 | for( int i = 0 ; i < iterations ; ++i ) 60 | { 61 | increase_count( increment ); 62 | increase_count( increment ); 63 | } 64 | const auto baseline_stop_free_function = std::chrono::steady_clock::now(); 65 | 66 | count_value = 0; 67 | const auto baseline_start_std_function = std::chrono::steady_clock::now(); 68 | for( int i = 0 ; i < iterations ; ++i ) 69 | { 70 | increase_count_std_function( increment ); 71 | increase_count_std_function( increment ); 72 | } 73 | const auto baseline_stop_std_function = std::chrono::steady_clock::now(); 74 | 75 | count_value = 0; 76 | const auto baseline_start_lambda = std::chrono::steady_clock::now(); 77 | for( int i = 0 ; i < iterations ; ++i ) 78 | { 79 | increase_count_lambda_function( increment ); 80 | increase_count_lambda_function( increment ); 81 | } 82 | const auto baseline_stop_lambda = std::chrono::steady_clock::now(); 83 | 84 | count_value = 0; 85 | const auto baseline_start_functor = std::chrono::steady_clock::now(); 86 | for( int i = 0 ; i < iterations ; ++i ) 87 | { 88 | increase_count_functor( increment ); 89 | increase_count_functor( increment ); 90 | } 91 | const auto baseline_stop_functor = std::chrono::steady_clock::now(); 92 | 93 | count_value = 0; 94 | const auto baseline_start_member_function = std::chrono::steady_clock::now(); 95 | for( int i = 0 ; i < iterations ; ++i ) 96 | { 97 | increase_member_function.increase_count( increment ); 98 | increase_member_function.increase_count( increment ); 99 | } 100 | const auto baseline_stop_member_function = std::chrono::steady_clock::now(); 101 | 102 | // With observers 103 | count_value = 0; 104 | pg::subject< int > subject_free_function; 105 | owner.connect( subject_free_function, increase_count ); 106 | owner.connect( subject_free_function, increase_count ); 107 | const auto start_free_function = std::chrono::steady_clock::now(); 108 | for( int i = 0 ; i < iterations ; ++i ) 109 | { 110 | subject_free_function.notify( increment ); 111 | } 112 | const auto stop_free_function = std::chrono::steady_clock::now(); 113 | 114 | count_value = 0; 115 | pg::subject< int > subject_std_function; 116 | owner.connect( subject_std_function, increase_count_std_function ); 117 | owner.connect( subject_std_function, increase_count_std_function ); 118 | const auto start_std_function = std::chrono::steady_clock::now(); 119 | for( int i = 0 ; i < iterations ; ++i ) 120 | { 121 | subject_std_function.notify( increment ); 122 | } 123 | const auto stop_std_function = std::chrono::steady_clock::now(); 124 | 125 | count_value = 0; 126 | pg::subject< int > subject_lambda; 127 | owner.connect( subject_lambda, [&]( int value ){ count_value += value; } ); 128 | owner.connect( subject_lambda, [&]( int value ){ count_value += value; } ); 129 | const auto start_lambda = std::chrono::steady_clock::now(); 130 | for( int i = 0 ; i < iterations ; ++i ) 131 | { 132 | subject_lambda.notify( increment ); 133 | } 134 | const auto stop_lambda = std::chrono::steady_clock::now(); 135 | 136 | count_value = 0; 137 | pg::subject< int > subject_functor; 138 | owner.connect( subject_functor, increase_count_functor ); 139 | owner.connect( subject_functor, increase_count_functor ); 140 | const auto start_functor = std::chrono::steady_clock::now(); 141 | for( int i = 0 ; i < iterations ; ++i ) 142 | { 143 | subject_functor.notify( increment ); 144 | } 145 | const auto stop_functor = std::chrono::steady_clock::now(); 146 | 147 | count_value = 0; 148 | pg::subject< int > subject_member_function; 149 | owner.connect( subject_member_function, &increase_member_function, &increase::increase_count ); 150 | owner.connect( subject_member_function, &increase_member_function, &increase::increase_count ); 151 | const auto start_member_function = std::chrono::steady_clock::now(); 152 | for( int i = 0 ; i < iterations ; ++i ) 153 | { 154 | subject_member_function.notify( increment ); 155 | } 156 | const auto stop_member_function = std::chrono::steady_clock::now(); 157 | 158 | 159 | struct result 160 | { 161 | using time_point = std::chrono::steady_clock::time_point; 162 | using time = std::chrono::duration< float, std::micro >; 163 | 164 | const float m_base_time; 165 | const float m_time; 166 | const float m_difference; 167 | 168 | result( const time_point & base_start, 169 | const time_point & base_stop, 170 | const time_point & start, 171 | const time_point & stop ) 172 | : m_base_time( time( base_stop - base_start ).count() ) 173 | , m_time( time( stop - start ).count() ) 174 | , m_difference( time( stop - start ).count() / time( base_stop - base_start ).count() ) 175 | {} 176 | }; 177 | 178 | auto free_function_result = result( baseline_start_free_function, baseline_stop_free_function, start_free_function, stop_free_function ); 179 | auto std_function_result = result( baseline_start_std_function, baseline_stop_std_function, start_std_function, stop_std_function ); 180 | auto lambda_result = result( baseline_start_lambda, baseline_stop_lambda, start_lambda, stop_lambda ); 181 | auto functor_result = result( baseline_start_functor, baseline_stop_functor, start_functor, stop_functor ); 182 | auto member_function_result = result( baseline_start_member_function, baseline_stop_member_function, start_member_function, stop_member_function ); 183 | 184 | printf( 185 | "|--------------------------------------------------------|\n" 186 | "| | baseline | observer | difference |\n" 187 | "|--------------------------------------------------------|\n" 188 | "| free function | %10.2f | %10.2f | %9.2fx |\n" 189 | "| std::function | %10.2f | %10.2f | %9.2fx |\n" 190 | "| lambda | %10.2f | %10.2f | %9.2fx |\n" 191 | "| functor | %10.2f | %10.2f | %9.2fx |\n" 192 | "| member function | %10.2f | %10.2f | %9.2fx |\n" 193 | "|--------------------------------------------------------|\n", 194 | free_function_result.m_base_time, free_function_result.m_time, free_function_result.m_difference, 195 | std_function_result.m_base_time, std_function_result.m_time, std_function_result.m_difference, 196 | lambda_result.m_base_time, lambda_result.m_time, lambda_result.m_difference, 197 | functor_result.m_base_time, functor_result.m_time, functor_result.m_difference, 198 | member_function_result.m_base_time, member_function_result.m_time, member_function_result.m_difference ); 199 | 200 | return 0; 201 | } 202 | -------------------------------------------------------------------------------- /vs2019/Observer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29411.108 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "observer", "observer.vcxproj", "{06A03865-7E6C-4804-86E1-8E13A82B0C32}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "chain_subjects", "chain_subjects.vcxproj", "{CA073B63-C0E9-4D9F-A796-D40B783B053F}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "connect_function_objects", "connect_function_objects.vcxproj", "{0FEBA0AE-205A-4094-9888-545F79EFD04F}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "connect_member_function", "connect_member_function.vcxproj", "{C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "disconnect_from_connection_owner", "disconnect_from_connection_owner.vcxproj", "{57057C0D-6277-49FE-A0F6-78E898F238C5}" 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hello_world", "hello_world.vcxproj", "{196CD1D1-4B1F-427A-8712-99FEEC82D310}" 17 | EndProject 18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "inherit_from_owner", "inherit_from_owner.vcxproj", "{34B17083-0A9F-4950-9745-A5FB82B5C8F1}" 19 | EndProject 20 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "subject_blocker", "subject_blocker.vcxproj", "{0E0D922F-87D0-42F6-8266-C63A4D5B8D40}" 21 | EndProject 22 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "variable_argument_count", "variable_argument_count.vcxproj", "{CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}" 23 | EndProject 24 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests.vcxproj", "{83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}" 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{21872FB9-E886-4098-BC9C-62ABC9C3CEB0}" 27 | EndProject 28 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "benchmark", "benchmark.vcxproj", "{933638EE-696B-41C9-BA51-7D72D236D3CE}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|x64 = Debug|x64 33 | Debug|x86 = Debug|x86 34 | Release|x64 = Release|x64 35 | Release|x86 = Release|x86 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {06A03865-7E6C-4804-86E1-8E13A82B0C32}.Debug|x64.ActiveCfg = Debug|x64 39 | {06A03865-7E6C-4804-86E1-8E13A82B0C32}.Debug|x64.Build.0 = Debug|x64 40 | {06A03865-7E6C-4804-86E1-8E13A82B0C32}.Debug|x86.ActiveCfg = Debug|Win32 41 | {06A03865-7E6C-4804-86E1-8E13A82B0C32}.Debug|x86.Build.0 = Debug|Win32 42 | {06A03865-7E6C-4804-86E1-8E13A82B0C32}.Release|x64.ActiveCfg = Release|x64 43 | {06A03865-7E6C-4804-86E1-8E13A82B0C32}.Release|x64.Build.0 = Release|x64 44 | {06A03865-7E6C-4804-86E1-8E13A82B0C32}.Release|x86.ActiveCfg = Release|Win32 45 | {06A03865-7E6C-4804-86E1-8E13A82B0C32}.Release|x86.Build.0 = Release|Win32 46 | {CA073B63-C0E9-4D9F-A796-D40B783B053F}.Debug|x64.ActiveCfg = Debug|x64 47 | {CA073B63-C0E9-4D9F-A796-D40B783B053F}.Debug|x64.Build.0 = Debug|x64 48 | {CA073B63-C0E9-4D9F-A796-D40B783B053F}.Debug|x86.ActiveCfg = Debug|Win32 49 | {CA073B63-C0E9-4D9F-A796-D40B783B053F}.Debug|x86.Build.0 = Debug|Win32 50 | {CA073B63-C0E9-4D9F-A796-D40B783B053F}.Release|x64.ActiveCfg = Release|x64 51 | {CA073B63-C0E9-4D9F-A796-D40B783B053F}.Release|x64.Build.0 = Release|x64 52 | {CA073B63-C0E9-4D9F-A796-D40B783B053F}.Release|x86.ActiveCfg = Release|Win32 53 | {CA073B63-C0E9-4D9F-A796-D40B783B053F}.Release|x86.Build.0 = Release|Win32 54 | {0FEBA0AE-205A-4094-9888-545F79EFD04F}.Debug|x64.ActiveCfg = Debug|x64 55 | {0FEBA0AE-205A-4094-9888-545F79EFD04F}.Debug|x64.Build.0 = Debug|x64 56 | {0FEBA0AE-205A-4094-9888-545F79EFD04F}.Debug|x86.ActiveCfg = Debug|Win32 57 | {0FEBA0AE-205A-4094-9888-545F79EFD04F}.Debug|x86.Build.0 = Debug|Win32 58 | {0FEBA0AE-205A-4094-9888-545F79EFD04F}.Release|x64.ActiveCfg = Release|x64 59 | {0FEBA0AE-205A-4094-9888-545F79EFD04F}.Release|x64.Build.0 = Release|x64 60 | {0FEBA0AE-205A-4094-9888-545F79EFD04F}.Release|x86.ActiveCfg = Release|Win32 61 | {0FEBA0AE-205A-4094-9888-545F79EFD04F}.Release|x86.Build.0 = Release|Win32 62 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}.Debug|x64.ActiveCfg = Debug|x64 63 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}.Debug|x64.Build.0 = Debug|x64 64 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}.Debug|x86.ActiveCfg = Debug|Win32 65 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}.Debug|x86.Build.0 = Debug|Win32 66 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}.Release|x64.ActiveCfg = Release|x64 67 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}.Release|x64.Build.0 = Release|x64 68 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}.Release|x86.ActiveCfg = Release|Win32 69 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC}.Release|x86.Build.0 = Release|Win32 70 | {57057C0D-6277-49FE-A0F6-78E898F238C5}.Debug|x64.ActiveCfg = Debug|x64 71 | {57057C0D-6277-49FE-A0F6-78E898F238C5}.Debug|x64.Build.0 = Debug|x64 72 | {57057C0D-6277-49FE-A0F6-78E898F238C5}.Debug|x86.ActiveCfg = Debug|Win32 73 | {57057C0D-6277-49FE-A0F6-78E898F238C5}.Debug|x86.Build.0 = Debug|Win32 74 | {57057C0D-6277-49FE-A0F6-78E898F238C5}.Release|x64.ActiveCfg = Release|x64 75 | {57057C0D-6277-49FE-A0F6-78E898F238C5}.Release|x64.Build.0 = Release|x64 76 | {57057C0D-6277-49FE-A0F6-78E898F238C5}.Release|x86.ActiveCfg = Release|Win32 77 | {57057C0D-6277-49FE-A0F6-78E898F238C5}.Release|x86.Build.0 = Release|Win32 78 | {196CD1D1-4B1F-427A-8712-99FEEC82D310}.Debug|x64.ActiveCfg = Debug|x64 79 | {196CD1D1-4B1F-427A-8712-99FEEC82D310}.Debug|x64.Build.0 = Debug|x64 80 | {196CD1D1-4B1F-427A-8712-99FEEC82D310}.Debug|x86.ActiveCfg = Debug|Win32 81 | {196CD1D1-4B1F-427A-8712-99FEEC82D310}.Debug|x86.Build.0 = Debug|Win32 82 | {196CD1D1-4B1F-427A-8712-99FEEC82D310}.Release|x64.ActiveCfg = Release|x64 83 | {196CD1D1-4B1F-427A-8712-99FEEC82D310}.Release|x64.Build.0 = Release|x64 84 | {196CD1D1-4B1F-427A-8712-99FEEC82D310}.Release|x86.ActiveCfg = Release|Win32 85 | {196CD1D1-4B1F-427A-8712-99FEEC82D310}.Release|x86.Build.0 = Release|Win32 86 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1}.Debug|x64.ActiveCfg = Debug|x64 87 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1}.Debug|x64.Build.0 = Debug|x64 88 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1}.Debug|x86.ActiveCfg = Debug|Win32 89 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1}.Debug|x86.Build.0 = Debug|Win32 90 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1}.Release|x64.ActiveCfg = Release|x64 91 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1}.Release|x64.Build.0 = Release|x64 92 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1}.Release|x86.ActiveCfg = Release|Win32 93 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1}.Release|x86.Build.0 = Release|Win32 94 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40}.Debug|x64.ActiveCfg = Debug|x64 95 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40}.Debug|x64.Build.0 = Debug|x64 96 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40}.Debug|x86.ActiveCfg = Debug|Win32 97 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40}.Debug|x86.Build.0 = Debug|Win32 98 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40}.Release|x64.ActiveCfg = Release|x64 99 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40}.Release|x64.Build.0 = Release|x64 100 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40}.Release|x86.ActiveCfg = Release|Win32 101 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40}.Release|x86.Build.0 = Release|Win32 102 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}.Debug|x64.ActiveCfg = Debug|x64 103 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}.Debug|x64.Build.0 = Debug|x64 104 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}.Debug|x86.ActiveCfg = Debug|Win32 105 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}.Debug|x86.Build.0 = Debug|Win32 106 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}.Release|x64.ActiveCfg = Release|x64 107 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}.Release|x64.Build.0 = Release|x64 108 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}.Release|x86.ActiveCfg = Release|Win32 109 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952}.Release|x86.Build.0 = Release|Win32 110 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}.Debug|x64.ActiveCfg = Debug|x64 111 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}.Debug|x64.Build.0 = Debug|x64 112 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}.Debug|x86.ActiveCfg = Debug|Win32 113 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}.Debug|x86.Build.0 = Debug|Win32 114 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}.Release|x64.ActiveCfg = Release|x64 115 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}.Release|x64.Build.0 = Release|x64 116 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}.Release|x86.ActiveCfg = Release|Win32 117 | {83CB54E0-2E3B-43F9-A1D0-FF8CD0AC50BE}.Release|x86.Build.0 = Release|Win32 118 | {933638EE-696B-41C9-BA51-7D72D236D3CE}.Debug|x64.ActiveCfg = Debug|x64 119 | {933638EE-696B-41C9-BA51-7D72D236D3CE}.Debug|x64.Build.0 = Debug|x64 120 | {933638EE-696B-41C9-BA51-7D72D236D3CE}.Debug|x86.ActiveCfg = Debug|Win32 121 | {933638EE-696B-41C9-BA51-7D72D236D3CE}.Debug|x86.Build.0 = Debug|Win32 122 | {933638EE-696B-41C9-BA51-7D72D236D3CE}.Release|x64.ActiveCfg = Release|x64 123 | {933638EE-696B-41C9-BA51-7D72D236D3CE}.Release|x64.Build.0 = Release|x64 124 | {933638EE-696B-41C9-BA51-7D72D236D3CE}.Release|x86.ActiveCfg = Release|Win32 125 | {933638EE-696B-41C9-BA51-7D72D236D3CE}.Release|x86.Build.0 = Release|Win32 126 | EndGlobalSection 127 | GlobalSection(SolutionProperties) = preSolution 128 | HideSolutionNode = FALSE 129 | EndGlobalSection 130 | GlobalSection(NestedProjects) = preSolution 131 | {CA073B63-C0E9-4D9F-A796-D40B783B053F} = {21872FB9-E886-4098-BC9C-62ABC9C3CEB0} 132 | {0FEBA0AE-205A-4094-9888-545F79EFD04F} = {21872FB9-E886-4098-BC9C-62ABC9C3CEB0} 133 | {C5D017B5-9AD8-46D6-A3C5-19C7E4E5F4CC} = {21872FB9-E886-4098-BC9C-62ABC9C3CEB0} 134 | {57057C0D-6277-49FE-A0F6-78E898F238C5} = {21872FB9-E886-4098-BC9C-62ABC9C3CEB0} 135 | {196CD1D1-4B1F-427A-8712-99FEEC82D310} = {21872FB9-E886-4098-BC9C-62ABC9C3CEB0} 136 | {34B17083-0A9F-4950-9745-A5FB82B5C8F1} = {21872FB9-E886-4098-BC9C-62ABC9C3CEB0} 137 | {0E0D922F-87D0-42F6-8266-C63A4D5B8D40} = {21872FB9-E886-4098-BC9C-62ABC9C3CEB0} 138 | {CAE2B2FF-FFC7-4CDF-95D5-E90B52C30952} = {21872FB9-E886-4098-BC9C-62ABC9C3CEB0} 139 | EndGlobalSection 140 | GlobalSection(ExtensibilityGlobals) = postSolution 141 | SolutionGuid = {65445BF5-868B-4A73-A151-242A04EDA6ED} 142 | EndGlobalSection 143 | EndGlobal 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # observer 2 | 3 | An observer pattern / signal slot pattern that can ignore extra parameters like Qt's signals and slots. 4 | 5 | ## Features 6 | 7 | * Connected callables can accept _less_ parameters than the subject's interface provides. 8 | * Defining the observer's notification values types by variadic template parameters. 9 | * Connect all kinds of callables to a subject; member functions, lambdas, functors, `std::function` and free functions. 10 | * Lifetime management of the connection between the subject and observer. 11 | * [1 header file](https://github.com/PG1003/observer/blob/master/src/observer.h) that includes only 2 headers from the standard template library. 12 | 13 | ## Requirements 14 | 15 | * Compiller that supports C++14 or newer. 16 | 17 | ## Goals 18 | 19 | * Easy to use. 20 | * Robust lifetime management. 21 | * Low overhead. 22 | * Customazation. 23 | 24 | ## Non-goals 25 | 26 | * Processing return values returned by the connected functions and observers. 27 | The return value is ignored for most cases where an observer pattern or signal/slot solution is used. 28 | It also limits the usage of this library and adds complexity; all observers or connected functions must return the same type and the results must becombined when receiving values from multiple observers. 29 | * Build-in mutexes/atomics/thread safety. 30 | Adding these will add complexity and impact the performance for cases where these are not needed. 31 | When needed, you can create custom subjects that integrates with your application. 32 | 33 | When you really need these features out-of-the-box then you may take a look at [boost signals](https://www.boost.org/doc/libs/1_72_0/doc/html/signals2.html). 34 | 35 | ## Benchmark 36 | 37 | There is a [benchmark](https://github.com/PG1003/observer/blob/master/benchmark/benchmark.cpp) that indicates how much the observer mechanism adds to the function call overhead, _not_ the total slowdown of a function. 38 | In real-world scenarios, the time spend in the body of the function often outweights the call overhead of a function. 39 | So in many cases the added function call overhead will be negletable. 40 | 41 | The table below are the results of a benchmark that was executed on a system with the following specifications; 42 | OS: Manjaro Linux 64-bit, CPU: i5-5250U, RAM: 16 GB LPDDR3-1866, Compiler: GCC 10.1.0, Compiler flags: -std=c++14 -Wall -Wextra -Wpedantic -O3. 43 | 44 | | | baseline | observer | difference | 45 | |---|----------|----------|------------| 46 | | free function | 243254.44 | 361678.09 | 1.49x | 47 | | std::function | 243558.03 | 522351.25 | 2.14x | 48 | | lambda | 241195.89 | 281690.34 | 1.17x | 49 | | functor | 240892.38 | 282730.97 | 1.17x | 50 | | member function| 240899.56 | 403652.97 | 1.68x | 51 | 52 | ## Examples 53 | 54 | In the [examples folder](https://github.com/PG1003/observer/blob/master/examples) you will find example programs that show the features and usage of this library. 55 | You can also take a peek in [tests.cpp](https://github.com/PG1003/observer/blob/master/test/tests.cpp). 56 | 57 | The following examples are provided to get the impression about the usage of this observer library. 58 | 59 | ### Connecting a lambda 60 | 61 | ``` c++ 62 | int main( int /* argc */, char * /* argv */[] ) 63 | { 64 | // 1 Define a subject that notifies without parameters. 65 | pg::subject<> hello_subject; 66 | 67 | // 2 Connect a lambda to the subject. 68 | // Assign the connection to a variable to keep the connection alive. 69 | // The connection will be automatically removed when the variable goes out of scope. 70 | auto connection = pg::connect( hello_subject, []{ std::cout << "Hello World!" << std::endl; } ); 71 | 72 | // 3 Notify the observers. 73 | // This will call the lambda that was connected to this subject. 74 | // In this case the subject has one function connected to it. 75 | hello_subject.notify(); 76 | 77 | return 0; 78 | } 79 | ``` 80 | The output is: 81 | >Hello World! 82 | 83 | ### Connecting a function that ignores the extra parameters from subject 84 | 85 | ```c++ 86 | // 1 A free function that accepts one string parameter. 87 | void hello( const std::string & str ) 88 | { 89 | std::cout << "Hello " << str << std::endl; 90 | } 91 | 92 | int main( int /* argc */, char * /* argv */[] ) 93 | { 94 | // 2 Define a subject that passes two values; a string and an integer. 95 | pg::subject< const char *, int > world_subject; 96 | 97 | // 4 Connect the hello function to the subject. 98 | // hello takes the first string value from the subject and ignors the second integer. 99 | // Assign the connection to a variable to keep the connection alive. 100 | auto connection = pg::connect( world_subject, hello ); 101 | 102 | // 5 Notify the observers. 103 | // This will call the hello function that was connected to this subject. 104 | world_subject.notify( "World!", 42 ); 105 | 106 | return 0; 107 | } 108 | ``` 109 | The output is: 110 | >Hello World! 111 | 112 | ### Connecting a member function 113 | 114 | ```c++ 115 | int main( int /* argc */, char * /* argv */[] ) 116 | { 117 | // 1 Define a subject that passes a string to its observers. 118 | pg::subject< const std::string & > s; 119 | 120 | // 2 A vector to store the values we receive from our subject. 121 | std::vector< std::string > v; 122 | 123 | // 3 Create an alias for the type of overloaded member function pointer that we want to connect. 124 | using overload = void( std::vector< std::string >::* )( const std::string & ); 125 | 126 | // 4 Connect the vector's push_back fuction to our subject. 127 | // We need to cast member function pointer to select the required overload. 128 | // Assign the connection to a variable to keep the connection alive. 129 | auto connection = pg::connect( s, &v, static_cast< overload >( &std::vector< std::string >::push_back ) ); 130 | 131 | // 5 Notify the observers. 132 | s.notify( "Hello" ); 133 | s.notify( "World!" ); 134 | 135 | // 6 Print the contents of the vector. 136 | for( auto& str : v ) 137 | { 138 | std::cout << str << std::endl; 139 | } 140 | 141 | return 0; 142 | } 143 | ``` 144 | The output is: 145 | >Hello 146 | >World! 147 | 148 | ### Manage multiple connections using a `pg::connection_owner` 149 | 150 | ```c++ 151 | int main( int /* argc */, char * /* argv */[] ) 152 | { 153 | // 1 Define the subject that passes a string to its observers. 154 | pg::subject< std::string > foo; 155 | 156 | { 157 | // 2 A connection owner within a new scope. 158 | // This demonstrates that the connections are removed when leaving scope. 159 | pg::connection_owner connections; 160 | 161 | // 3 Connect multiple lambda functions to the subject. 162 | connections.connect( foo, []( std::string_view message ) 163 | { 164 | std::cout << message << std::endl; 165 | } ); 166 | connections.connect( foo, []{ std::cout << "Hello World!" << std::endl; } ); 167 | 168 | // 4 Notify the observers. 169 | foo.notify( "Hello PG1003!" ); 170 | } 171 | 172 | // 5 The next notify prints nothing because the connection owner went out of scope. 173 | foo.notify( "How are you?" ); 174 | 175 | return 0; 176 | } 177 | ``` 178 | The output is: 179 | >Hello PG1003! 180 | >Hello World! 181 | 182 | ### Manage multiple connections by inheriting from `pg::connection_owner` 183 | 184 | ```c++ 185 | // 1 An object that inherits from pg::connection_owner. 186 | // Connections are made in the constructor and removed when the object goes out of scope. 187 | struct bar_object : public pg::connection_owner 188 | { 189 | bar_object( pg::subject< std::string > & foo ) 190 | { 191 | // 2 Connect member functions to the subject that is passed as constructor parameter. 192 | connect( foo, this, &bar_object::print ); 193 | connect( foo, this, &bar_object::print_bar ); 194 | } 195 | 196 | void print( std::string_view str ) { std::cout << str; } 197 | void print_bar() { std::cout << "bar" << std::endl; } 198 | }; 199 | 200 | int main( int /* argc */, char * /* argv */[] ) 201 | { 202 | // 3 Define the subject that passes a string to its observers. 203 | pg::subject< std::string > foo; 204 | 205 | { 206 | // 4 Create an object within a new scope that makes connections to the subject. 207 | // This demonstrates that the connections are removed when leaving scope. 208 | bar_object bar( foo ); 209 | 210 | // 5 Notify the observers. 211 | foo.notify( "foo" ); 212 | } 213 | 214 | // 6 The next notify prints nothing because the object went out of scope. 215 | foo.notify( "baz" ); 216 | 217 | return 0; 218 | } 219 | ``` 220 | The output is: 221 | >foobar 222 | 223 | ## Documentation 224 | 225 | ### Observer 226 | 227 | An observer is an object that implements an observer interface. 228 | The `connect` functions of this observer library implement observer interfaces when connecting member functions or callables to a subject. 229 | This saves you from implementing observer interfaces manually. 230 | 231 | You use this interface when implementing custom subjects and observers. 232 | 233 | #### Custom observers 234 | 235 | It is possible to implement your own observer types by inheriting from the observer interface. 236 | However then you should also handle the cases when a subject disconnect its observers, for example when a subject goes out of scope. 237 | 238 | The variadic template arguments of the observer interface defines the value types that an observer should receive at notification. 239 | 240 | ```c++ 241 | class my_observer final : public pg::observer< int > 242 | { 243 | public: 244 | virtual void disconnect() override; // Called by the subject when it disconnect its observers. 245 | virtual void notify( int arg ) override; // Called when the subject notifies its observers. 246 | }; 247 | ``` 248 | 249 | ### Subject 250 | 251 | A subject is an object that notifies its observers. 252 | 253 | This observer library contains two subject types; `pg::subject` and `pg::blockable_subject`. 254 | The latter of these two has a mechanism to temporary block notifications. 255 | 256 | For both subjects types you can define with variadic template parameters the value types to pass when notifying the observers. 257 | These template parameters also defines the observer interface which you can connect to the subject. 258 | 259 | ```c++ 260 | pg::subject< int, const char * > // A subject that passes an integer and a string when notifying its observers. 261 | pg::subject<> // A subject that notifies without values. 262 | ``` 263 | 264 | #### Custom subjects 265 | 266 | You can create custom subjects for applications that need tight integration, multiprocessing, low overhead, etc. 267 | 268 | To create custom subjects you need to define the following two public member functions to connect observers and facilitate lifetime management: 269 | * `[discarded] connect( pg::observer< T... > * ) [const]` 270 | * `[discarded] disconnect( [const] pg::observer< T... > * ) [const]` 271 | 272 | Also, you must call for each observer the `pg::observer< T... >::disconnect()` function before removing it from the object, for example in the destructor. 273 | 274 | The example below is a lightweight subject that handles only one observer. 275 | Note that there is _no_ `notify` function. 276 | The notify function is not a part of the static interface that defines a subject. 277 | So you can pick any name for the notification function that calls the observer's notify like `emit` or create your own method to notify the observer. 278 | 279 | ```c++ 280 | class my_subject 281 | { 282 | pg::observer< my_data > * m_observer = nullptr; 283 | 284 | public: 285 | ~subject_base() noexcept 286 | { 287 | if( m_observer ) 288 | { 289 | m_observer->disconnect(); 290 | } 291 | } 292 | 293 | void connect( pg::observer< my_data > * const o ) noexcept 294 | { 295 | if( m_observer ) 296 | { 297 | m_observer->disconnect(); 298 | } 299 | m_observer = o; 300 | } 301 | 302 | void disconnect( const pg::observer< my_data > * const o ) noexcept 303 | { 304 | if( o == m_observer ) 305 | { 306 | m_observer = nullptr; 307 | } 308 | } 309 | }; 310 | ``` 311 | 312 | ### Connection lifetime management 313 | 314 | Lifetime management of the connection between subjects and observers is important. 315 | It releases resources that are no longer needed and avoids accessing resources that are no longer available. 316 | When a subject is removed, all connections to that subject must be removed too. 317 | The same applies when removing an observer; the observer must be disconnected from its subject. 318 | 319 | There are two methods to manage the lifetime of connections between subjects and observers: 320 | * Scoped connection 321 | * Connection owner 322 | 323 | #### Scoped connections 324 | 325 | A scoped connection is a lightweight object of the `pg::scoped_connection` type which owns a connection. 326 | The lifetime of the connection ends when the lifetime of the subject ends or when the scoped connection object goes out of scope. 327 | 328 | Scoped connections are returned by the `pg::connect` functions when connecting a member function or a callable to a subject. 329 | This connection type can be used at places where a limited number of connections are maintained. 330 | 331 | ```c++ 332 | pg::subject< int > s; 333 | 334 | { 335 | // Create a scoped connection 336 | pg::scoped_connection connection = pg::connect( s, []( int i ){ std::cout << i << std::endl; } ); 337 | 338 | s.notify( 42 ); // Prints '42' in the output 339 | } 340 | 341 | s.notify( 1337 ); // Prints nothing, connection went out of scope 342 | ``` 343 | 344 | #### Connection owner 345 | 346 | A connection owner is usefull for places where a lot of connections needs to be managed. 347 | You can add a connection owner via composition by adding a `pg::connection_owner` member or give a object connection owner traits by deriving from it. 348 | The lifetime of a connection is bound to the connection owner's lifetime. 349 | Connections are automatically removed from the connection owner when the subject goes out of scope or gets deleted. 350 | 351 | A connection owner object owns only connections that are created with one of its `pg::connection_owner::connect` functions. 352 | Connection owners do not share connections. 353 | 354 | ```c++ 355 | pg::subject< int > s; 356 | 357 | { 358 | pg::connection_owner owner; 359 | owner.connect( s, []( int i ){ std::cout << i << std::endl; } ); 360 | owner.connect( s, []( int i ){ std::cout << ( i + i ) << std::endl; } ); 361 | 362 | s.notify( 21 ); // Prints '21' and '42' 363 | } 364 | 365 | s.notify( 1337 ); // Prints nothing, connection owner went out of scope 366 | ``` 367 | ### C++17 CTAD 368 | 369 | Although at least C++14 is required, C++17 introduced [CTAD](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction) which simplifies the use of `pg::subject_blocker` and makes defining a parameterless `pg::subject` prettier. 370 | 371 | ``` c++ 372 | pg::subject<> foo // C++14 373 | pg::subject foo; // C++17 374 | 375 | pg::blockable_subject<> bar; // C++14 376 | pg::subject_blocker< pg::blockable_subject<> > blocker( bar ); // C++14 377 | pg::subject_blocker blocker( bar ); // C++17 378 | ``` 379 | -------------------------------------------------------------------------------- /src/observer.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 PG1003 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | 28 | #ifdef __has_cpp_attribute 29 | # if __has_cpp_attribute( nodiscard ) 30 | # define PG_OBSERVER_NODISCARD [[nodiscard]] 31 | # endif 32 | # if __has_cpp_attribute( likely ) 33 | # define PG_OBSERVER_LIKELY [[likely]] 34 | # endif 35 | #endif 36 | #ifndef PG_OBSERVER_NODISCARD 37 | # define PG_OBSERVER_NODISCARD 38 | #endif 39 | #ifndef PG_OBSERVER_LIKELY 40 | # define PG_OBSERVER_LIKELY 41 | #endif 42 | 43 | namespace pg 44 | { 45 | 46 | namespace detail 47 | { 48 | 49 | // This base class is to type erase the observers so that different observer types can be collected in a container. 50 | class apex_observer 51 | { 52 | public: 53 | virtual ~apex_observer() noexcept = default; 54 | }; 55 | 56 | } 57 | 58 | /** 59 | * \brief Interface for observer objects. 60 | * 61 | * \tparam A Defines the types of the parameters for the observer's notify function. 62 | * 63 | * \see subject 64 | */ 65 | template< typename ...A > 66 | class observer : public detail::apex_observer 67 | { 68 | public: 69 | /** 70 | * \brief Called when the observer is disconnected from the subject. 71 | * 72 | * The subject calls disconnect on all its observers when the subject gets deleted or goes out of scope. 73 | */ 74 | virtual void disconnect() noexcept = 0; 75 | 76 | /** 77 | * \brief The notification function that is called when the subject notifies 78 | * 79 | * \param args The values of the notification. These are defined by this class' template parameters. 80 | */ 81 | virtual void notify( A... args ) = 0; 82 | }; 83 | 84 | namespace detail 85 | { 86 | 87 | // https://en.cppreference.com/w/cpp/types/remove_reference 88 | template< typename T > struct remove_reference { typedef T type; }; 89 | template< typename T > struct remove_reference< T & > { typedef T type; }; 90 | template< typename T > struct remove_reference< T && >{ typedef T type; }; 91 | 92 | // https://stackoverflow.com/a/27867127 93 | template< typename T > 94 | struct invoke_helper : invoke_helper< decltype( &detail::remove_reference< T >::type::operator() ) > {}; 95 | 96 | template< typename R, typename ...A > 97 | struct invoke_helper< R ( * )( A... ) > 98 | { 99 | template< typename F, typename ...Ar > 100 | static decltype( auto ) invoke( F&& function, A... args, Ar&&... ) 101 | { 102 | return function( std::forward< A >( args )... ); 103 | } 104 | }; 105 | 106 | template< typename R, typename C, typename ...A > 107 | struct invoke_helper< R ( C:: * )( A... ) > 108 | { 109 | template< typename F, typename ...Ar > 110 | static decltype( auto ) invoke( F&& function, A... args, Ar&&... ) 111 | { 112 | return function( std::forward< A >( args )... ); 113 | } 114 | }; 115 | 116 | template< typename R, typename C, typename ...A > 117 | struct invoke_helper< R ( C:: * )( A... ) const > 118 | { 119 | template< typename F, typename ...Ar > 120 | static decltype( auto ) invoke( F&& function, A... args, Ar&&... ) 121 | { 122 | return function( std::forward< A >( args )... ); 123 | } 124 | }; 125 | 126 | template< typename ...A > 127 | class subject_base 128 | { 129 | subject_base( const subject_base< A... > & ) = delete; 130 | subject_base< A... >& operator=( const subject_base< A... > & ) = delete; 131 | 132 | protected: 133 | std::vector< observer< A... > * > m_observers; 134 | 135 | subject_base() noexcept = default; 136 | 137 | ~subject_base() noexcept 138 | { 139 | for( auto it = m_observers.rbegin() ; it != m_observers.crend() ; ++it ) 140 | { 141 | ( *it )->disconnect(); 142 | } 143 | } 144 | 145 | public: 146 | void connect( observer< A... > * const o ) noexcept 147 | { 148 | m_observers.push_back( o ); 149 | } 150 | 151 | void disconnect( const observer< A... > * const o ) noexcept 152 | { 153 | // Iterate reversed over the m_observers since we expect that observers that 154 | // are frequently connected and disconnected resides at the end of the vector. 155 | auto it_find = std::find( m_observers.crbegin(), m_observers.crend(), o ); 156 | if( it_find != m_observers.crend() ) PG_OBSERVER_LIKELY 157 | { 158 | m_observers.erase( ( ++it_find ).base() ); 159 | } 160 | } 161 | }; 162 | 163 | } 164 | 165 | /** 166 | * \brief Invokes a callable with the given arguments. 167 | * 168 | * \param function A callable such as a free function, lambda, std::function or a functor. 169 | * \param args The arguments to forward to \em function. 170 | * 171 | * \return Returns the value that the invoked function returned. 172 | * 173 | * The advantage this implementation over std::invoke is that the number of parameters that is 174 | * forwarded to \em function is adjusted when \em function accept less parameters than passed to \em invoke. 175 | */ 176 | template< typename F, typename ...A > 177 | inline decltype( auto ) invoke( F&& function, A&&... args ) 178 | { 179 | return detail::invoke_helper< F >::invoke( std::forward< F >( function ), std::forward< A >( args )... ); 180 | } 181 | 182 | /** 183 | * \overload inline decltype( auto ) invoke( F&& function, A&&... args ) 184 | */ 185 | template< typename R, typename ...Af, typename ...A > 186 | inline decltype( auto ) invoke( R ( * function )( Af... ), A&&... args ) 187 | { 188 | return detail::invoke_helper< R ( * )( Af... ) >::invoke( std::forward< R ( * )( Af... ) >( function ), std::forward< A >( args )... ); 189 | } 190 | 191 | /** 192 | * \brief Class that calls the notification function of its observers. 193 | * 194 | * \tparam A The types of the values that are passed to the observers notification functions. 195 | * 196 | * \see observer 197 | */ 198 | template< typename ...A > 199 | class subject : public detail::subject_base< A... > 200 | { 201 | subject( const subject< A... > & ) = delete; 202 | subject< A... >& operator=( const subject< A... > & ) = delete; 203 | 204 | public: 205 | subject() noexcept = default; 206 | 207 | /** 208 | * \brief Notifies the observers observers connected to this subject. 209 | * 210 | * \param args The values passed to the observer's notification function. 211 | * 212 | * The observers are notified in the order they are connected. 213 | */ 214 | void notify( A... args ) const 215 | { 216 | for( auto o : detail::subject_base< A... >::m_observers ) 217 | { 218 | o->notify( args... ); 219 | } 220 | } 221 | }; 222 | 223 | /** 224 | * \brief Class that calls the notification function of its observers. 225 | * 226 | * \tparam A The types of the values that are passed to the observers notification functions. 227 | * 228 | * This subject type is blockable. 229 | * It maintains a block count that is tested before the notification of its observers. 230 | * This subject blocks notifications when the block count is non-zero. 231 | * 232 | * \see observer 233 | */ 234 | template< typename ...A > 235 | class blockable_subject : public detail::subject_base< A... > 236 | { 237 | blockable_subject( const blockable_subject< A... > & ) = delete; 238 | blockable_subject< A... >& operator=( const blockable_subject< A... > & ) = delete; 239 | 240 | int block_count = 0; 241 | 242 | public: 243 | blockable_subject() noexcept = default; 244 | 245 | /** 246 | * \brief Notifies the observers observers connected to this subject when not blocked. 247 | * 248 | * \param args The values passed to the observer's notification function. 249 | * 250 | * The observers are notified in the order they are connected. 251 | */ 252 | void notify( A... args ) const 253 | { 254 | if( !block_count ) 255 | { 256 | for( auto o : detail::subject_base< A... >::m_observers ) 257 | { 258 | o->notify( args... ); 259 | } 260 | } 261 | } 262 | 263 | /** 264 | * \brief Blocks the notification of its observers when called. 265 | * 266 | * This function can be called multiple times which increases a block count. 267 | * You must call \em unblock the same number of times to unblock the subject or call \em set_block_state( false ). 268 | * 269 | * Notifications are not buffered when the subject is blocked. 270 | * 271 | * \see blockable_subject::unblock blockable_subject::set_block_state 272 | */ 273 | void block() noexcept 274 | { 275 | ++block_count; 276 | } 277 | 278 | /** 279 | * \brief Decreases the block count and unblocks the subject when the count got 0. 280 | * 281 | * This function can be called multiple times even when the subject is already unblocked. 282 | * 283 | * \see blockable_subject::block blockable_subject::set_block_state 284 | */ 285 | void unblock() noexcept 286 | { 287 | if( block_count > 0 ) 288 | { 289 | --block_count; 290 | } 291 | } 292 | 293 | /** 294 | * \brief Overrides the block count when the state differs from the current block state. 295 | * 296 | * \param block_state The new block state. 297 | * 298 | * \return Returns the old block state. 299 | * 300 | * When the block count is 0 when the block_state is true, the block count is set to 1. 301 | * In case the block count is larger than 0 and the block_state is false, the block count is set to 0. 302 | * 303 | * The block count won't change if block_state is true and the block count is 0 or the block_state is false and the block count is 0. 304 | * 305 | * \see blockable_subject::block blockable_subject::unblock 306 | */ 307 | bool set_block_state( bool block_state ) noexcept 308 | { 309 | if( block_count && !block_state ) 310 | { 311 | block_count = 0; 312 | return true; 313 | } 314 | else if( !block_count && block_state ) 315 | { 316 | block_count = 1; 317 | return false; 318 | } 319 | 320 | return block_state; // No state change 321 | } 322 | }; 323 | 324 | /** 325 | * \brief Blocks temporary the notifications of a subject. 326 | * 327 | * \tparam S The type of the blockable subject. 328 | * 329 | * This class use RAII. The class blocks notifications when it is constructed and unblocked at destruction, 330 | * for example when a subject_blocker instance leaves its scope. 331 | * 332 | * A subject_blocker expects a blockable subject that has the following two methods; 333 | * - [discarded] block() [const] 334 | * - [discarded] unblock() [const] 335 | * 336 | * \see pg::blockable_subject 337 | */ 338 | template< typename S > 339 | class subject_blocker 340 | { 341 | S * m_subject = nullptr; 342 | 343 | public: 344 | subject_blocker() noexcept = default; 345 | 346 | /** 347 | * \param subject A reference to the subject which notifications must be blocked. 348 | */ 349 | subject_blocker( S & subject ) noexcept 350 | : m_subject( &subject ) 351 | { 352 | m_subject->block(); 353 | } 354 | 355 | ~subject_blocker() noexcept 356 | { 357 | if( m_subject ) 358 | { 359 | m_subject->unblock(); 360 | } 361 | } 362 | }; 363 | 364 | namespace detail 365 | { 366 | 367 | template< template< class, class, class... > class D, typename B, typename S > 368 | struct observer_type_factory : observer_type_factory< D, B, decltype( &S::connect ) > 369 | {}; 370 | 371 | template< template< class, class, class... > class D, typename B, typename R, typename S, typename ...Ao > 372 | struct observer_type_factory< D, B, R ( S:: * )( observer< Ao... > * ) noexcept > 373 | { 374 | using type = D< B, S, Ao... >; 375 | }; 376 | 377 | template< template< class, class, class... > class D, typename B, typename R, typename S, typename ...Ao > 378 | struct observer_type_factory< D, B, R ( S:: * )( observer< Ao... > * ) const noexcept > 379 | { 380 | using type = D< B, S, Ao... >; 381 | }; 382 | 383 | template< typename O, typename F, typename ...Ao > 384 | class member_function_observer 385 | { 386 | O * const m_instance; 387 | F m_function; 388 | 389 | protected: 390 | member_function_observer( O * instance, F function ) noexcept 391 | : m_instance( instance ) 392 | , m_function( function ) 393 | {} 394 | 395 | template< typename ...Ar > 396 | void invoke( Ao&&... args, Ar&&... ) 397 | { 398 | ( m_instance->*m_function )( std::forward< Ao >( args )... ); 399 | } 400 | }; 401 | 402 | template< typename F > 403 | class function_observer 404 | { 405 | F m_function; 406 | 407 | protected: 408 | function_observer( F&& function ) noexcept 409 | : m_function( std::forward< F >( function ) ) 410 | {} 411 | 412 | template< typename ...As > 413 | void invoke( As&&... args ) 414 | { 415 | detail::invoke_helper< F >::invoke( m_function, std::forward< As >( args )... ); 416 | } 417 | }; 418 | 419 | } 420 | 421 | /** 422 | * \brief Manages the subject <--> observer connection lifetime from the observer side. 423 | * 424 | * Connections that are made and thus owned by connection_owner are removed at destruction of connection_owner. 425 | * 426 | * When connecting an observer to a subject, the connection_owner expects that a subject has the following methods; 427 | * - [discarded] connect( pg::observer< T... > * ) [const] 428 | * - [discarded] disconnect( [const] pg::observer< T... > * ) [const] 429 | * 430 | * \see pg::subject pg::blockable_subject pg::observer 431 | */ 432 | class connection_owner 433 | { 434 | class abstract_observer 435 | { 436 | public: 437 | virtual ~abstract_observer() noexcept = default; 438 | virtual void remove_from_subject() noexcept = 0; 439 | }; 440 | 441 | template< typename B, typename S, typename ...Ao > 442 | class owner_observer final : public observer< Ao... >, public abstract_observer, B 443 | { 444 | connection_owner &m_owner; 445 | S &m_subject; 446 | 447 | virtual void notify( Ao... args ) override 448 | { 449 | B::invoke( std::forward< Ao >( args )... ); 450 | } 451 | 452 | virtual void disconnect() noexcept override 453 | { 454 | m_owner.remove_observer( this ); 455 | } 456 | 457 | virtual void remove_from_subject() noexcept override 458 | { 459 | m_subject.disconnect( this ); 460 | } 461 | 462 | public: 463 | template< typename ...Ab > 464 | owner_observer( connection_owner &owner, S &subject, Ab&&... args_base ) noexcept 465 | : B( std::forward< Ab >( args_base )... ) 466 | , m_owner( owner ) 467 | , m_subject( subject ) 468 | { 469 | m_owner.add_observer( this ); 470 | m_subject.connect( this ); 471 | } 472 | }; 473 | 474 | connection_owner( const connection_owner & ) = delete; 475 | connection_owner & operator=( const connection_owner & ) = delete; 476 | 477 | std::vector< abstract_observer * > m_observers; 478 | 479 | auto find_observer( const abstract_observer * const o ) 480 | { 481 | auto it_find = std::find( m_observers.crbegin(), m_observers.crend(), o ); 482 | 483 | return it_find == m_observers.crend() ? m_observers.cend() : ( ++it_find ).base(); 484 | } 485 | 486 | void remove_observer( abstract_observer * const o ) noexcept 487 | { 488 | auto it_find = find_observer( o ); 489 | if( it_find != m_observers.cend() ) PG_OBSERVER_LIKELY 490 | { 491 | m_observers.erase( it_find ); 492 | delete o; 493 | } 494 | } 495 | 496 | void add_observer( abstract_observer * const o ) noexcept 497 | { 498 | m_observers.push_back( o ); 499 | } 500 | 501 | public: 502 | /** 503 | * \brief A handle to a subject <--> observer connection. 504 | * 505 | * \note Reuse of this handle may lead to undefined behavior. 506 | * 507 | * \see pg::connection_owner::connect pg::connection_owner::disconnect 508 | */ 509 | class connection 510 | { 511 | friend connection_owner; 512 | abstract_observer * m_h = nullptr; 513 | 514 | connection( abstract_observer * h ) 515 | : m_h( h ) 516 | {} 517 | 518 | public: 519 | connection() noexcept = default; 520 | }; 521 | 522 | connection_owner() = default; 523 | 524 | ~connection_owner() noexcept 525 | { 526 | for( auto it = m_observers.crbegin() ; it != m_observers.crend() ; ++it ) 527 | { 528 | ( *it )->remove_from_subject(); 529 | delete *it; 530 | } 531 | } 532 | 533 | /** 534 | * \brief Connects a member function of an object to a subject. 535 | * 536 | * \param s The subject from which the object's member function will receive notifications. 537 | * \param instance The instance of the object. 538 | * \param function The member function pointer that is called when the subject notifies 539 | * 540 | * \return Returns a connection_owner::connection handle. 541 | * 542 | * The number of parameters that \em function accepts can be less than the number of values that comes with the notification. 543 | * 544 | * \note The lifetime of the instance must exceed the connection_owner's lifetime. 545 | */ 546 | template< typename S, typename R, typename O, typename ...Ao > 547 | connection connect( S &s, O * instance, R ( O::* const function )( Ao... ) ) noexcept 548 | { 549 | using observer_type = typename detail::observer_type_factory< owner_observer, detail::member_function_observer< O, R ( O::* )( Ao... ), Ao... >, S >::type; 550 | return new observer_type( *this, s, instance, function ); 551 | } 552 | 553 | /** 554 | * \overload connect( S & s, O * instance, R( O::* )( Ao... ) function ) 555 | */ 556 | template< typename S, typename R, typename O, typename ...Ao > 557 | connection connect( S &s, O * instance, R ( O::* const function )( Ao... ) const ) noexcept 558 | { 559 | using observer_type = typename detail::observer_type_factory< owner_observer, detail::member_function_observer< O, R ( O::* )( Ao... ) const, Ao... >, S >::type; 560 | return new observer_type( *this, s, instance, function ); 561 | } 562 | 563 | /** 564 | * \overload connect( S & s, O * instance, R( O::* )( Ao... ) function ) 565 | */ 566 | template< typename S, typename R, typename O, typename ...Ao > 567 | connection connect( S &s, const O * instance, R ( O::* const function )( Ao... ) const ) noexcept 568 | { 569 | using observer_type = typename detail::observer_type_factory< owner_observer, detail::member_function_observer< const O, R ( O::* )( Ao... ) const, Ao... >, S >::type; 570 | return new observer_type( *this, s, instance, function ); 571 | } 572 | 573 | /** 574 | * \brief Connects a callable to a subject. 575 | * 576 | * \param s The subject from which \em function will receive notifications. 577 | * \param function A callable such as a free function, lambda, std::function or a functor that is called when the subject notifies. 578 | * 579 | * \return Returns a connection_owner::connection handle. 580 | * 581 | * The number of parameters that \em function accepts can be less than the number of values that comes with the notification. 582 | * 583 | * \note The \em function is copied and stored in the connection_owner. This means that the callables must have a copy constructor. 584 | * 585 | * \note When a callable has side effects than the lifetime of these side effects must exceed the connection_owner's lifetime. 586 | */ 587 | template< typename S, typename F > 588 | connection connect( S &s, F&& function ) noexcept 589 | { 590 | using observer_type = typename detail::observer_type_factory< owner_observer, detail::function_observer< F >, S >::type; 591 | return new observer_type( *this, s, std::forward< F >( function ) ); 592 | } 593 | 594 | /** 595 | * \overload connection connect( S &s, F function ) noexcept 596 | */ 597 | template< typename S, typename R, typename ...Af > 598 | connection connect( S &s, R ( * function )( Af... ) ) noexcept 599 | { 600 | using observer_type = typename detail::observer_type_factory< owner_observer, detail::function_observer< R ( * )( Af... ) >, S >::type; 601 | return new observer_type( *this, s, std::forward< R ( * )( Af... ) >( function ) ); 602 | } 603 | 604 | /** 605 | * \brief Disconnects the observer from its subject. 606 | * 607 | * \param c The connection handle. 608 | * 609 | * The observer will be deleted in case it is a lambda, std::function or a functor. 610 | * 611 | * \see pg::connection_owner::connect 612 | */ 613 | void disconnect( connection c ) noexcept 614 | { 615 | auto it_find = find_observer( c.m_h ); 616 | if( it_find != m_observers.cend() ) PG_OBSERVER_LIKELY 617 | { 618 | c.m_h->remove_from_subject(); 619 | m_observers.erase( it_find ); 620 | delete c.m_h; 621 | } 622 | } 623 | }; 624 | 625 | /** 626 | * \brief Defined for backwards compatibility. 627 | * 628 | * observer_owner was renamed to connection_owner. 629 | * The name 'connection_owner' expresses better what the object does; owning the lifetime of the connections. 630 | */ 631 | using observer_owner [[deprecated]] = connection_owner; 632 | 633 | namespace detail 634 | { 635 | 636 | template< typename B, typename S, typename ...Ao > 637 | class scoped_observer final : public observer< Ao... >, B 638 | { 639 | S * m_subject; 640 | 641 | virtual void notify( Ao... args ) override 642 | { 643 | B::invoke( std::forward< Ao >( args )... ); 644 | } 645 | 646 | virtual void disconnect() noexcept override 647 | { 648 | m_subject = nullptr; 649 | } 650 | 651 | public: 652 | template< typename ...Ab > 653 | scoped_observer( S &subject, Ab&&... args_base ) noexcept 654 | : B( std::forward< Ab >( args_base )... ) 655 | , m_subject( &subject ) 656 | { 657 | m_subject->connect( this ); 658 | } 659 | 660 | virtual ~scoped_observer() 661 | { 662 | if( m_subject ) 663 | { 664 | m_subject->disconnect( this ); 665 | } 666 | } 667 | }; 668 | 669 | } 670 | 671 | /** 672 | * \brief Owns a connection between subject <--> observer. 673 | * 674 | * A pg::scoped_connection is created by the pg::connect functions. 675 | * The lifetime of the connection owned by pg::scoped_connection and ends when a pg::scoped_connection goes out of scope or gets deleted. 676 | * 677 | * When assigning a new connection returned by pg::connect, the connection it currently holds will be deleted before taking ownership of the new one. 678 | * 679 | * \note The lifetime of a scoped_connection must exceed the connected (member) function's side-effects' lifetime at the observer side. 680 | * 681 | * \see pg::connect 682 | */ 683 | class scoped_connection 684 | { 685 | template< typename S, typename R, typename O, typename ...Ao > 686 | friend scoped_connection connect( S &s, O * instance, R ( O::* const function )( Ao... ) ) noexcept; 687 | 688 | template< typename S, typename R, typename O, typename ...Ao > 689 | friend scoped_connection connect( S &s, O * instance, R ( O::* const function )( Ao... ) const ) noexcept; 690 | 691 | template< typename S, typename R, typename O, typename ...Ao > 692 | friend scoped_connection connect( S &s, const O * instance, R ( O::* const function )( Ao... ) const ) noexcept; 693 | 694 | template< typename S, typename R, typename ...Af > 695 | friend scoped_connection connect( S &s, R ( * function )( Af... ) ) noexcept; 696 | 697 | template< typename S, typename F > 698 | friend scoped_connection connect( S &s, F&& function ) noexcept; 699 | 700 | detail::apex_observer* m_observer = nullptr; 701 | 702 | scoped_connection( detail::apex_observer * o ) noexcept 703 | : m_observer( o ) 704 | {} 705 | 706 | public: 707 | scoped_connection() = default; 708 | 709 | scoped_connection( scoped_connection&& other ) noexcept 710 | { 711 | delete m_observer; 712 | m_observer = other.m_observer; 713 | other.m_observer = nullptr; 714 | } 715 | 716 | scoped_connection& operator=( scoped_connection&& other ) noexcept 717 | { 718 | delete m_observer; 719 | m_observer = other.m_observer; 720 | other.m_observer = nullptr; 721 | return *this; 722 | } 723 | 724 | ~scoped_connection() noexcept 725 | { 726 | delete m_observer; 727 | } 728 | 729 | /** 730 | * \brief Ends the lifetime its connection. 731 | */ 732 | void reset() noexcept 733 | { 734 | delete m_observer; 735 | m_observer = nullptr; 736 | } 737 | }; 738 | 739 | /** 740 | * \brief Connects a member function of an object to a subject. 741 | * 742 | * \param s The subject from which the object's member function will receive notifications. 743 | * \param instance The instance of the object. 744 | * \param function The member function pointer that is called when the subject notifies 745 | * 746 | * \return Returns a pg::scoped_connection. 747 | * 748 | * The number of parameters that \em function accepts can be less than the number of values that comes with the notification. 749 | * 750 | * \note The lifetime of the instance must exceed the scoped_connection's lifetime. 751 | */ 752 | template< typename S, typename R, typename O, typename ...Ao > 753 | PG_OBSERVER_NODISCARD scoped_connection connect( S &s, O * instance, R ( O::* const function )( Ao... ) ) noexcept 754 | { 755 | using observer_type = typename detail::observer_type_factory< detail::scoped_observer, detail::member_function_observer< O, R ( O::* )( Ao... ), Ao... >, S >::type; 756 | return new observer_type( s, instance, function ); 757 | } 758 | 759 | /** 760 | * \overload connect( S & s, O * instance, R( O::* )( Ao... ) function ) 761 | */ 762 | template< typename S, typename R, typename O, typename ...Ao > 763 | PG_OBSERVER_NODISCARD scoped_connection connect( S &s, O * instance, R ( O::* const function )( Ao... ) const ) noexcept 764 | { 765 | using observer_type = typename detail::observer_type_factory< detail::scoped_observer, detail::member_function_observer< O, R ( O::* )( Ao... ) const, Ao... >, S >::type; 766 | return new observer_type( s, instance, function ); 767 | } 768 | 769 | /** 770 | * \overload connect( S & s, O * instance, R( O::* )( Ao... ) function ) 771 | */ 772 | template< typename S, typename R, typename O, typename ...Ao > 773 | PG_OBSERVER_NODISCARD scoped_connection connect( S &s, const O * instance, R ( O::* const function )( Ao... ) const ) noexcept 774 | { 775 | using observer_type = typename detail::observer_type_factory< detail::scoped_observer, detail::member_function_observer< const O, R ( O::* )( Ao... ) const, Ao... >, S >::type; 776 | return new observer_type( s, instance, function ); 777 | } 778 | 779 | /** 780 | * \brief Connects a callable to a subject. 781 | * 782 | * \param s The subject from which \em function will receive notifications. 783 | * \param function A callable such as a free function, lambda, std::function or a functor that is called when the subject notifies. 784 | * 785 | * \return Returns a pg::scoped_connection. 786 | * 787 | * The number of parameters that \em function accepts can be less than the number of values that comes with the notification. 788 | * 789 | * \note The \em function is copied and stored in the connection_owner. This means that the callables must have a copy constructor. 790 | * 791 | * \note When a callable has side effects than the lifetime of these side effects must exceed the scoped_connection's lifetime. 792 | */ 793 | template< typename S, typename F > 794 | PG_OBSERVER_NODISCARD scoped_connection connect( S &s, F&& function ) noexcept 795 | { 796 | using observer_type = typename detail::observer_type_factory< detail::scoped_observer, detail::function_observer< F >, S >::type; 797 | return new observer_type( s, std::forward< F >( function ) ); 798 | } 799 | 800 | /** 801 | * \overload scoped_connection connect( S &s, F&& function ) 802 | */ 803 | template< typename S, typename R, typename ...Af > 804 | PG_OBSERVER_NODISCARD scoped_connection connect( S &s, R ( * function )( Af... ) ) noexcept 805 | { 806 | using observer_type = typename detail::observer_type_factory< detail::scoped_observer, detail::function_observer< R ( * )( Af... ) >, S >::type; 807 | return new observer_type( s, std::forward< R ( * )( Af... ) >( function ) ); 808 | } 809 | 810 | } 811 | 812 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #if __cplusplus >= 201703L 5 | #include 6 | #endif 7 | #include 8 | #include 9 | #include 10 | 11 | static int total_asserts = 0; 12 | static int failed_asserts = 0; 13 | 14 | static bool report_failed_assert( const char* const file, const int line, const char* const condition ) 15 | { 16 | std::cout << "assert failed! (file " << file << ", line " << line << "): " << condition << std::endl; 17 | ++failed_asserts; 18 | return false; 19 | } 20 | 21 | #define assert_true( c ) do { ++total_asserts; ( c ) || report_failed_assert( __FILE__, __LINE__, #c ); } while( false ); 22 | 23 | 24 | using namespace pg; 25 | 26 | /////////////////////////////////////////////////////////////////////////////// 27 | // Functions and structs used by the tests // 28 | /////////////////////////////////////////////////////////////////////////////// 29 | 30 | static int free_function_int_val = -1; 31 | static void free_function_int( int i ) 32 | { 33 | free_function_int_val = i; 34 | } 35 | 36 | static int free_function_void_val = 0; 37 | static void free_function_void() 38 | { 39 | ++free_function_void_val; 40 | } 41 | 42 | static void free_function_reset() 43 | { 44 | free_function_int_val = -1; 45 | free_function_void_val = 0; 46 | } 47 | 48 | static int free_function_return_int() 49 | { 50 | return 42; 51 | } 52 | 53 | struct functor_int 54 | { 55 | int &val; 56 | 57 | functor_int( int &i ) 58 | : val( i ) 59 | {} 60 | 61 | void operator()( int i ) 62 | { 63 | val = i; 64 | } 65 | }; 66 | 67 | struct functor_void 68 | { 69 | int &val; 70 | 71 | functor_void( int &i ) 72 | : val( i ) 73 | {} 74 | 75 | void operator()() 76 | { 77 | ++val; 78 | } 79 | }; 80 | 81 | struct functor_return_int 82 | { 83 | int operator()( int i ) 84 | { 85 | return i; 86 | } 87 | }; 88 | 89 | struct const_functor_return_int 90 | { 91 | int operator()( int i ) const 92 | { 93 | return i; 94 | } 95 | }; 96 | 97 | struct member_observers 98 | { 99 | int int_char_ival = -1; 100 | int int_char_cval = -1; 101 | int int_ival = -1; 102 | int void_val = 0; 103 | 104 | void int_char( int i, char c ) 105 | { 106 | int_char_ival = i; 107 | int_char_cval = c; 108 | } 109 | 110 | void int_( int i ) 111 | { 112 | int_ival = i; 113 | } 114 | 115 | void void_() 116 | { 117 | ++void_val; 118 | } 119 | }; 120 | 121 | struct member_observers_with_owner : private connection_owner, public member_observers 122 | { 123 | member_observers_with_owner( subject< int, char > &subject_int_char ) 124 | { 125 | connect( subject_int_char, static_cast< member_observers * >( this ), &member_observers::int_char ); 126 | connect( subject_int_char, static_cast< member_observers * >( this ), &member_observers::int_ ); 127 | connect( subject_int_char, static_cast< member_observers * >( this ), &member_observers::void_ ); 128 | } 129 | }; 130 | 131 | /////////////////////////////////////////////////////////////////////////////// 132 | // Tests // 133 | /////////////////////////////////////////////////////////////////////////////// 134 | 135 | static void free_function_observer() 136 | { 137 | free_function_reset(); 138 | 139 | subject< int > subject_int; 140 | subject<> subject_void; 141 | 142 | { 143 | connection_owner owner; 144 | 145 | owner.connect( subject_int, free_function_int ); 146 | owner.connect( subject_int, free_function_void ); 147 | owner.connect( subject_void, free_function_void ); 148 | 149 | subject_int.notify( 42 ); 150 | assert_true( free_function_int_val == 42 ); 151 | assert_true( free_function_void_val == 1 ); 152 | 153 | subject_void.notify(); 154 | assert_true( free_function_void_val == 2 ); 155 | } 156 | 157 | free_function_reset(); 158 | 159 | { 160 | auto c1 = connect( subject_int, free_function_int ); 161 | auto c2 = connect( subject_int, free_function_void ); 162 | auto c3 = connect( subject_void, free_function_void ); 163 | 164 | subject_int.notify( 42 ); 165 | assert_true( free_function_int_val == 42 ); 166 | assert_true( free_function_void_val == 1 ); 167 | 168 | subject_void.notify(); 169 | assert_true( free_function_void_val == 2 ); 170 | } 171 | } 172 | 173 | static void lambda_function_observer() 174 | { 175 | subject< int > subject_int; 176 | subject<> subject_void; 177 | 178 | int lambda_int_val = -1; 179 | int lambda_void_val = 0; 180 | 181 | { 182 | connection_owner owner; 183 | 184 | owner.connect( subject_int, [ & ]( int i ){ lambda_int_val = i; } ); 185 | owner.connect( subject_int, [ & ]{ ++lambda_void_val; } ); 186 | owner.connect( subject_void, [ & ]{ ++lambda_void_val; } ); 187 | 188 | subject_int.notify( 42 ); 189 | assert_true( lambda_int_val == 42 ); 190 | assert_true( lambda_void_val == 1 ); 191 | 192 | subject_void.notify(); 193 | assert_true( lambda_void_val == 2 ); 194 | } 195 | 196 | lambda_int_val = -1; 197 | lambda_void_val = 0; 198 | 199 | { 200 | auto c1 = connect( subject_int, [ & ]( int i ){ lambda_int_val = i; } ); 201 | auto c2 = connect( subject_int, [ & ]{ ++lambda_void_val; } ); 202 | auto c3 = connect( subject_void, [ & ]{ ++lambda_void_val; } ); 203 | 204 | subject_int.notify( 42 ); 205 | assert_true( lambda_int_val == 42 ); 206 | assert_true( lambda_void_val == 1 ); 207 | 208 | subject_void.notify(); 209 | assert_true( lambda_void_val == 2 ); 210 | } 211 | } 212 | 213 | static void std_function_observer() 214 | { 215 | subject< int > subject_int; 216 | subject<> subject_void; 217 | 218 | int lambda_int_val = -1; 219 | int lambda_void_val = 0; 220 | 221 | std::function< void( int ) > std_function_int = [ & ]( int i ){ lambda_int_val = i; }; 222 | std::function< void() > std_function_void = [ & ]{ ++lambda_void_val; }; 223 | 224 | { 225 | connection_owner owner; 226 | 227 | owner.connect( subject_int, std_function_int ); 228 | owner.connect( subject_int, std_function_void ); 229 | owner.connect( subject_void, std_function_void ); 230 | 231 | subject_int.notify( 1337 ); 232 | assert_true( lambda_int_val == 1337 ); 233 | assert_true( lambda_void_val == 1 ); 234 | 235 | subject_void.notify(); 236 | assert_true( lambda_void_val == 2 ); 237 | } 238 | 239 | lambda_int_val = -1; 240 | lambda_void_val = 0; 241 | 242 | { 243 | auto c1 = connect( subject_int, std_function_int ); 244 | auto c2 = connect( subject_int, std_function_void ); 245 | auto c3 = connect( subject_void, std_function_void ); 246 | 247 | subject_int.notify( 1337 ); 248 | assert_true( lambda_int_val == 1337 ); 249 | assert_true( lambda_void_val == 1 ); 250 | 251 | subject_void.notify(); 252 | assert_true( lambda_void_val == 2 ); 253 | } 254 | } 255 | 256 | static void functor_observer() 257 | { 258 | subject< int > subject_int; 259 | 260 | int int_val = -1; 261 | int void_val = 0; 262 | 263 | { 264 | connection_owner owner; 265 | 266 | owner.connect( subject_int, functor_int( int_val ) ); 267 | owner.connect( subject_int, functor_void( void_val ) ); 268 | 269 | subject_int.notify( 1003 ); 270 | assert_true( int_val == 1003 ); 271 | assert_true( void_val == 1 ); 272 | } 273 | 274 | int_val = -1; 275 | void_val = 0; 276 | 277 | { 278 | auto c1 = connect( subject_int, functor_int( int_val ) ); 279 | auto c2 = connect( subject_int, functor_void( void_val ) ); 280 | 281 | subject_int.notify( 1003 ); 282 | assert_true( int_val == 1003 ); 283 | assert_true( void_val == 1 ); 284 | } 285 | } 286 | 287 | static void member_function_observer() 288 | { 289 | subject< int, char > subject_int_char; 290 | 291 | { 292 | member_observers_with_owner member_observers_with_owner_( subject_int_char ); 293 | 294 | subject_int_char.notify( 1337, 'Q' ); 295 | assert_true( member_observers_with_owner_.int_char_ival == 1337 ); 296 | assert_true( member_observers_with_owner_.int_char_cval == 'Q' ); 297 | assert_true( member_observers_with_owner_.int_ival == 1337 ); 298 | assert_true( member_observers_with_owner_.void_val == 1 ); 299 | } 300 | 301 | { 302 | member_observers member_observers_; 303 | 304 | auto c1 = connect( subject_int_char, &member_observers_, &member_observers_with_owner::int_char ); 305 | auto c2 = connect( subject_int_char, &member_observers_, &member_observers_with_owner::int_ ); 306 | auto c3 = connect( subject_int_char, &member_observers_, &member_observers_with_owner::void_ ); 307 | 308 | subject_int_char.notify( 1337, 'Q' ); 309 | assert_true( member_observers_.int_char_ival == 1337 ); 310 | assert_true( member_observers_.int_char_cval == 'Q' ); 311 | assert_true( member_observers_.int_ival == 1337 ); 312 | assert_true( member_observers_.void_val == 1 ); 313 | } 314 | } 315 | 316 | 317 | static void subject_subject_observer() 318 | { 319 | subject< int, char > subject_int_char1; 320 | subject< int, char > subject_int_char2; 321 | subject< int > subject_int; 322 | subject<> subject_void; 323 | 324 | int int_char_1_ival = -1; 325 | char int_char_1_cval = '\0'; 326 | int int_char_2_ival = -1; 327 | char int_char_2_cval = '\0'; 328 | int int_val = -1; 329 | int void_val = 0; 330 | 331 | { 332 | connection_owner owner; 333 | 334 | owner.connect( subject_int_char1, [ & ]( int i, char c ){ int_char_1_ival = i; int_char_1_cval = c; } ); 335 | owner.connect( subject_int_char1, &subject_int_char2, &subject< int, char >::notify ); 336 | owner.connect( subject_int_char2, [ & ]( int i, char c ){ int_char_2_ival = i; int_char_2_cval = c; } ); 337 | owner.connect( subject_int_char2, &subject_int, &subject< int >::notify ); 338 | owner.connect( subject_int, [ & ]( int i ){ int_val = i; } ); 339 | owner.connect( subject_int, &subject_void, &subject<>::notify ); 340 | owner.connect( subject_void, [ & ]{ ++void_val; } ); 341 | 342 | subject_int_char1.notify( 33, 'R' ); 343 | assert_true( int_char_1_ival == 33 ); 344 | assert_true( int_char_1_cval == 'R' ); 345 | assert_true( int_char_2_ival == 33 ); 346 | assert_true( int_char_2_cval == 'R' ); 347 | assert_true( int_val == 33 ); 348 | assert_true( void_val == 1 ); 349 | } 350 | 351 | int_char_1_ival = -1; 352 | int_char_1_cval = '\0'; 353 | int_char_2_ival = -1; 354 | int_char_2_cval = '\0'; 355 | int_val = -1; 356 | void_val = 0; 357 | 358 | { 359 | auto c1 = connect( subject_int_char1, [ & ]( int i, char c ){ int_char_1_ival = i; int_char_1_cval = c; } ); 360 | auto c2 = connect( subject_int_char1, &subject_int_char2, &subject< int, char >::notify ); 361 | auto c3 = connect( subject_int_char2, [ & ]( int i, char c ){ int_char_2_ival = i; int_char_2_cval = c; } ); 362 | auto c4 = connect( subject_int_char2, &subject_int, &subject< int >::notify ); 363 | auto c5 = connect( subject_int, [ & ]( int i ){ int_val = i; } ); 364 | auto c6 = connect( subject_int, &subject_void, &subject<>::notify ); 365 | auto c7 = connect( subject_void, [ & ]{ ++void_val; } ); 366 | 367 | subject_int_char1.notify( 33, 'R' ); 368 | assert_true( int_char_1_ival == 33 ); 369 | assert_true( int_char_1_cval == 'R' ); 370 | assert_true( int_char_2_ival == 33 ); 371 | assert_true( int_char_2_cval == 'R' ); 372 | assert_true( int_val == 33 ); 373 | assert_true( void_val == 1 ); 374 | } 375 | 376 | } 377 | 378 | static void observer_owner_lifetime() 379 | { 380 | subject< int, char > subject_int_char; 381 | 382 | int val = -1; 383 | 384 | { 385 | connection_owner owner; 386 | 387 | owner.connect( subject_int_char, [ & ]( int i ){ val = i; } ); 388 | 389 | subject_int_char.notify( 1701, 'J' ); 390 | assert_true( val == 1701 ); 391 | } 392 | 393 | subject_int_char.notify( 1702, 'K' ); 394 | assert_true( val == 1701 ); 395 | 396 | val = -1; 397 | 398 | { 399 | auto c = connect( subject_int_char, [ & ]( int i ){ val = i; } ); 400 | 401 | subject_int_char.notify( 1701, 'J' ); 402 | assert_true( val == 1701 ); 403 | } 404 | 405 | subject_int_char.notify( 1702, 'K' ); 406 | assert_true( val == 1701 ); 407 | } 408 | 409 | static void subject_lifetime() 410 | { 411 | int val_1 = 0; 412 | int val_2 = 0; 413 | 414 | { 415 | connection_owner owner; 416 | 417 | { 418 | subject<> subject_void; 419 | owner.connect( subject_void, [ & ]{ ++val_1; } ); 420 | 421 | subject_void.notify(); 422 | } 423 | 424 | subject<> subject_void; 425 | owner.connect( subject_void, [ & ]{ ++val_2; } ); 426 | 427 | subject_void.notify(); 428 | assert_true( val_1 == 1 ); 429 | assert_true( val_2 == 1 ); 430 | } 431 | 432 | val_1 = 0; 433 | val_2 = 0; 434 | 435 | { 436 | scoped_connection c1; 437 | 438 | { 439 | subject<> subject_void; 440 | c1 = connect( subject_void, [ & ]{ ++val_1; } ); 441 | 442 | subject_void.notify(); 443 | } 444 | 445 | subject<> subject_void; 446 | auto c2 = connect( subject_void, [ & ]{ ++val_2; } ); 447 | 448 | subject_void.notify(); 449 | assert_true( val_1 == 1 ); 450 | assert_true( val_2 == 1 ); 451 | } 452 | } 453 | 454 | static void scoped_observer() 455 | { 456 | scoped_connection connection; 457 | subject< int > s; 458 | 459 | int val = 0; 460 | 461 | connection = connect( s, [ & ]( int i ){ val = i; } ); 462 | 463 | s.notify( 42 ); 464 | assert_true( val == 42 ); 465 | 466 | { 467 | scoped_connection moved_connection( std::move( connection ) ); 468 | 469 | s.notify( 1337 ); 470 | assert_true( val == 1337 ); 471 | } 472 | 473 | s.notify( 1003 ); 474 | assert_true( val == 1337 ); 475 | 476 | connection = connect( s, [&]{ ++val; } ); 477 | 478 | s.notify( 42 ); 479 | assert_true( val == 1338 ); 480 | 481 | struct foo 482 | { 483 | int * const m_val = nullptr; 484 | 485 | foo( int & v ) 486 | : m_val( &v ) 487 | {} 488 | 489 | int operator()( int i ) 490 | { 491 | *m_val = i * 2; 492 | return *m_val; 493 | } 494 | 495 | ~foo() 496 | { 497 | *m_val = 0; 498 | } 499 | }; 500 | 501 | connection = connect( s, foo( val ) ); 502 | 503 | s.notify( 21 ); 504 | assert_true( val == 42 ); 505 | 506 | connection.reset(); 507 | assert_true( val == 0 ); 508 | } 509 | 510 | static void observer_disconnect() 511 | { 512 | connection_owner owner_1; 513 | connection_owner owner_2; 514 | subject<> subject_void; 515 | 516 | int val = 0; 517 | 518 | connection_owner::connection connection_1; 519 | connection_1 = owner_1.connect( subject_void, [ & ]{ ++val; } ); 520 | connection_owner::connection connection_2 = owner_2.connect( subject_void, [ & ]{ ++val; } ); 521 | 522 | owner_2.disconnect( connection_1 ); 523 | owner_1.disconnect( connection_2 ); 524 | 525 | subject_void.notify(); 526 | assert_true( val == 2 ); 527 | 528 | owner_1.disconnect( connection_1 ); 529 | owner_2.disconnect( connection_2 ); 530 | 531 | subject_void.notify(); 532 | assert_true( val == 2 ); 533 | } 534 | 535 | static void observer_notify_and_disconnect_order() 536 | { 537 | struct test_observer final : public observer<> 538 | { 539 | int &m_counter; 540 | const int m_expected_value; 541 | 542 | test_observer( int &counter, int expected_value ) noexcept 543 | : m_counter( counter ) 544 | , m_expected_value( expected_value ) 545 | {} 546 | 547 | virtual void notify() override 548 | { 549 | ++m_counter; 550 | assert_true( m_counter == m_expected_value ); 551 | } 552 | 553 | virtual void disconnect() noexcept override 554 | { 555 | assert_true( m_counter == m_expected_value ); 556 | --m_counter; 557 | } 558 | }; 559 | 560 | int shared_counter = 0; 561 | 562 | test_observer to_1( shared_counter, 1 ); 563 | test_observer to_2( shared_counter, 2 ); 564 | test_observer to_3( shared_counter, 3 ); 565 | 566 | subject<> s; 567 | s.connect( &to_1 ); 568 | s.connect( &to_2 ); 569 | s.connect( &to_3 ); 570 | 571 | s.notify(); 572 | } 573 | 574 | static void block_subject() 575 | { 576 | connection_owner owner; 577 | blockable_subject<> subject_void; 578 | 579 | int val = 0; 580 | 581 | owner.connect( subject_void, [ & ]{ ++val; } ); 582 | 583 | subject_void.notify(); 584 | assert_true( val == 1 ); 585 | 586 | { 587 | subject_blocker< blockable_subject<> > blocker( subject_void ); 588 | 589 | subject_void.notify(); 590 | assert_true( val == 1 ); 591 | } 592 | 593 | subject_void.notify(); 594 | assert_true( val == 2 ); 595 | 596 | subject_void.block(); 597 | subject_void.block(); 598 | 599 | subject_void.notify(); 600 | assert_true( val == 2 ); 601 | 602 | subject_void.set_block_state( false ); 603 | 604 | subject_void.notify(); 605 | assert_true( val == 3 ); 606 | 607 | subject_void.unblock(); 608 | 609 | subject_void.notify(); 610 | assert_true( val == 4 ); 611 | 612 | subject_void.set_block_state( true ); 613 | 614 | subject_void.notify(); 615 | assert_true( val == 4 ); 616 | } 617 | 618 | static void type_compatibility() 619 | { 620 | connection_owner owner; 621 | subject< std::string > subject_string; 622 | subject< const std::string > subject_const_string; 623 | subject< const std::string & > subject_const_string_ref; 624 | subject< char * > subject_p_char; 625 | subject< const char * > subject_const_p_char; 626 | 627 | int int_str = 0; 628 | int int_const_string_ref = 0; 629 | int int_p_char = 0; 630 | int int_const_p_char = 0; 631 | 632 | const auto int_reset = [ & ] 633 | { 634 | int_str = 0; 635 | int_const_string_ref = 0; 636 | int_p_char = 0; 637 | int_const_p_char = 0; 638 | }; 639 | 640 | std::string string_value = "Foobar"; 641 | const std::string const_string_value = "Foobar"; 642 | char sz_char[] = "Foobar"; 643 | const char *const_p_char_value = "Foobar"; 644 | 645 | const auto str = [ & ]( std::string str ){ if( str == const_string_value ) ++int_str; }; 646 | const auto const_str_ref = [ & ]( const std::string& str ){ if( str == const_string_value ) ++int_const_string_ref; }; 647 | const auto p_char = [ & ]( char *str ){ if( str == const_string_value ) ++int_p_char; }; 648 | const auto const_p_char = [ & ]( const char *str ){ if( str == const_string_value ) ++int_const_p_char; }; 649 | 650 | owner.connect( subject_string, str ); 651 | owner.connect( subject_string, const_str_ref ); 652 | 653 | owner.connect( subject_const_string, str ); 654 | owner.connect( subject_const_string, const_str_ref ); 655 | 656 | owner.connect( subject_const_string_ref, str ); 657 | owner.connect( subject_const_string_ref, const_str_ref ); 658 | 659 | owner.connect( subject_p_char, str ); 660 | owner.connect( subject_p_char, const_str_ref ); 661 | owner.connect( subject_p_char, p_char ); 662 | owner.connect( subject_p_char, const_p_char ); 663 | 664 | owner.connect( subject_const_p_char, str ); 665 | owner.connect( subject_const_p_char, const_str_ref ); 666 | owner.connect( subject_const_p_char, const_p_char ); 667 | 668 | subject_string.notify( "Foobar" ); 669 | subject_string.notify( string_value ); 670 | subject_string.notify( const_string_value ); 671 | subject_string.notify( sz_char ); 672 | subject_string.notify( const_p_char_value ); 673 | assert_true( int_str == 5 ); 674 | assert_true( int_const_string_ref == 5 ); 675 | int_reset(); 676 | 677 | subject_const_string.notify( "Foobar" ); 678 | subject_const_string.notify( string_value ); 679 | subject_const_string.notify( const_string_value ); 680 | subject_const_string.notify( sz_char ); 681 | subject_const_string.notify( const_p_char_value ); 682 | assert_true( int_str == 5 ); 683 | assert_true( int_const_string_ref == 5 ); 684 | int_reset(); 685 | 686 | subject_const_string_ref.notify( "Foobar" ); 687 | subject_const_string_ref.notify( string_value ); 688 | subject_const_string_ref.notify( const_string_value ); 689 | subject_const_string_ref.notify( sz_char ); 690 | subject_const_string_ref.notify( const_p_char_value ); 691 | assert_true( int_str == 5 ); 692 | assert_true( int_const_string_ref == 5 ); 693 | int_reset(); 694 | 695 | subject_p_char.notify( sz_char ); 696 | assert_true( int_str == 1 ); 697 | assert_true( int_const_string_ref == 1 ); 698 | assert_true( int_p_char == 1 ); 699 | assert_true( int_const_p_char == 1 ); 700 | int_reset(); 701 | 702 | subject_const_p_char.notify( "Foobar" ); 703 | subject_const_p_char.notify( sz_char ); 704 | subject_const_p_char.notify( const_p_char_value ); 705 | assert_true( int_str == 3 ); 706 | assert_true( int_const_string_ref == 3 ); 707 | assert_true( int_const_p_char == 3 ); 708 | } 709 | 710 | static void invoke_function() 711 | { 712 | // Free functions 713 | free_function_reset(); 714 | 715 | invoke( free_function_void, "pg", 1003 ); 716 | assert_true( free_function_void_val == 1 ); 717 | 718 | invoke( free_function_int, 42 ); 719 | assert_true( free_function_int_val == 42 ); 720 | 721 | const int int_free_function = invoke( free_function_return_int ); 722 | assert_true( int_free_function == 42 ); 723 | 724 | // Functor 725 | int int_functor_0 = -1; 726 | invoke( functor_int( int_functor_0 ), 42 ); 727 | assert_true( int_functor_0 == 42 ); 728 | 729 | const int int_functor_1 = invoke( functor_return_int(), 42, "foobar" ); 730 | assert_true( int_functor_1 == 42 ); 731 | 732 | const int int_functor_2 = invoke( functor_return_int(), 42 ); 733 | assert_true( int_functor_2 == 42 ); 734 | 735 | // Functor with const function 736 | const int int_const_functor_0 = invoke( const_functor_return_int(), 42, "foobar" ); 737 | assert_true( int_const_functor_0 == 42 ); 738 | 739 | const int int_const_functor_1 = invoke( const_functor_return_int(), 42 ); 740 | assert_true( int_const_functor_1 == 42 ); 741 | 742 | // Lambda 743 | const int int_lambda = invoke( []( int i ){ return i * 2; }, 21, 1337 ); 744 | assert_true( int_lambda == 42 ); 745 | 746 | // std::function 747 | int int_std_function = -1; 748 | const std::function< void( void ) > std_function = [&]{ int_std_function = 42; }; 749 | pg::invoke( std_function ); // ADL kicked in... explicit use pg::invoke to avoid ambiguity with std::invoke. 750 | assert_true( int_std_function == 42 ); 751 | } 752 | 753 | struct object_forwarding 754 | { 755 | object_forwarding() = delete; 756 | object_forwarding( const object_forwarding & ) = delete; 757 | object_forwarding( const object_forwarding && ) = delete; 758 | object_forwarding & operator =( const object_forwarding & ) = delete; 759 | object_forwarding & operator =( const object_forwarding && ) = delete; 760 | 761 | object_forwarding( int value ) 762 | : m_value( value ) 763 | {} 764 | 765 | const int m_value; 766 | }; 767 | 768 | void free_function_object_forwarding( const object_forwarding &o ) 769 | { 770 | assert_true( o.m_value == 1003 ); 771 | } 772 | 773 | struct member_functions_object_forwarding 774 | { 775 | void operator()( const object_forwarding &o ) 776 | { 777 | assert_true( o.m_value == 1003 ); 778 | } 779 | 780 | void foo( const object_forwarding &o ) 781 | { 782 | assert_true( o.m_value == 1003 ); 783 | } 784 | 785 | void bar( const object_forwarding &o ) const 786 | { 787 | assert_true( o.m_value == 1003 ); 788 | } 789 | }; 790 | 791 | struct const_member_functions_object_forwarding 792 | { 793 | void operator()( const object_forwarding &o ) const 794 | { 795 | assert_true( o.m_value == 1003 ); 796 | } 797 | 798 | void bar( const object_forwarding &o ) const 799 | { 800 | assert_true( o.m_value == 1003 ); 801 | } 802 | }; 803 | 804 | static void const_and_forwarding() 805 | { 806 | subject< const object_forwarding & > s; 807 | pg::connection_owner owner; 808 | 809 | member_functions_object_forwarding member_functions; 810 | const member_functions_object_forwarding member_functions_const; 811 | const_member_functions_object_forwarding const_member_functions; 812 | const const_member_functions_object_forwarding const_member_functions_const; 813 | 814 | owner.connect( s, []( const object_forwarding &o ) 815 | { 816 | assert_true( o.m_value == 1003 ); 817 | } ); 818 | owner.connect( s, free_function_object_forwarding ); 819 | 820 | owner.connect( s, member_functions ); 821 | owner.connect( s, &member_functions, &member_functions_object_forwarding::foo ); 822 | owner.connect( s, &member_functions, &member_functions_object_forwarding::bar ); 823 | 824 | owner.connect( s, &member_functions_const, &member_functions_object_forwarding::bar ); 825 | 826 | owner.connect( s, const_member_functions ); 827 | owner.connect( s, &const_member_functions, &const_member_functions_object_forwarding::bar ); 828 | 829 | owner.connect( s, const_member_functions_const ); 830 | owner.connect( s, &const_member_functions_const, &const_member_functions_object_forwarding::bar ); 831 | 832 | const auto c1 = pg::connect( s, []( const object_forwarding &o ) 833 | { 834 | assert_true( o.m_value == 1003 ); 835 | } ); 836 | const auto c2 = pg::connect( s, free_function_object_forwarding ); 837 | 838 | const auto c3 = pg::connect( s, member_functions ); 839 | const auto c4 = pg::connect( s, &member_functions, &member_functions_object_forwarding::foo ); 840 | const auto c5 = pg::connect( s, &member_functions, &member_functions_object_forwarding::bar ); 841 | 842 | const auto c6 = pg::connect( s, &member_functions_const, &member_functions_object_forwarding::bar ); 843 | 844 | const auto c7 = pg::connect( s, const_member_functions ); 845 | const auto c8 = pg::connect( s, &const_member_functions, &const_member_functions_object_forwarding::bar ); 846 | 847 | const auto c9 = pg::connect( s, const_member_functions_const ); 848 | const auto c10 = pg::connect( s, &const_member_functions_const, &const_member_functions_object_forwarding::bar ); 849 | 850 | s.notify( object_forwarding( 1003 ) ); 851 | } 852 | 853 | static std::string hello_called; 854 | static void hello( const std::string & str ) 855 | { 856 | hello_called = "Hello " + str; 857 | } 858 | 859 | void readme_examples() 860 | { 861 | // Connecting a lambda 862 | { 863 | std::string hello_world_called; 864 | 865 | pg::subject<> hello_subject; 866 | 867 | auto connection = pg::connect( hello_subject, [&]{ hello_world_called = "Hello World!"; } ); 868 | 869 | hello_subject.notify(); 870 | 871 | assert_true( hello_world_called == "Hello World!" ); 872 | } 873 | 874 | // Connecting a function that ignores the extra parameters from subject 875 | { 876 | pg::subject< const char *, int > world_subject; 877 | 878 | auto connection = pg::connect( world_subject, hello ); 879 | 880 | world_subject.notify( "World!", 42 ); 881 | 882 | assert_true( hello_called == "Hello World!" ); 883 | } 884 | 885 | // Connecting a member function 886 | { 887 | pg::subject< const std::string & > s; 888 | 889 | std::vector< std::string > v; 890 | 891 | using overload = void( std::vector< std::string >::* )( const std::string & ); 892 | 893 | auto connection = pg::connect( s, &v, static_cast< overload >( &std::vector< std::string >::push_back ) ); 894 | 895 | s.notify( "Hello" ); 896 | s.notify( "World!" ); 897 | 898 | assert_true( v.size() == 2 ); 899 | assert_true( v[ 0 ] == "Hello" ); 900 | assert_true( v[ 1 ] == "World!" ); 901 | } 902 | 903 | #if __cplusplus >= 201703L 904 | // Manage multiple connections using a `pg::connection_owner` 905 | { 906 | pg::subject< std::string > foo; 907 | 908 | std::string first_called; 909 | std::string second_called; 910 | 911 | { 912 | pg::connection_owner connections; 913 | 914 | connections.connect( foo, [&]( std::string_view message ) 915 | { 916 | first_called = message.substr(); 917 | } ); 918 | 919 | connections.connect( foo, [&] 920 | { 921 | second_called = "Hello World!"; 922 | } ); 923 | 924 | foo.notify( "Hello PG1003!" ); 925 | 926 | assert_true( first_called == "Hello PG1003!" ); 927 | assert_true( second_called == "Hello World!" ); 928 | } 929 | 930 | first_called.clear(); 931 | second_called.clear(); 932 | 933 | // 5 The connection owner object went out of scope and disconnected the observer functions. 934 | // The next notify prints nothing. 935 | foo.notify( "How are you?" ); 936 | 937 | assert_true( first_called.empty() ); 938 | assert_true( second_called.empty() ); 939 | } 940 | 941 | // Manage multiple connections by inheriting from `pg::connection_owner` 942 | { 943 | std::string print_called; 944 | std::string print_bar_called; 945 | 946 | struct bar_object : public pg::connection_owner 947 | { 948 | std::string & m_print_called; 949 | std::string & m_print_bar_called; 950 | 951 | bar_object( pg::subject< std::string > & foo, std::string & pc, std::string & pbc ) 952 | : m_print_called( pc ) 953 | , m_print_bar_called( pbc ) 954 | { 955 | connect( foo, this, &bar_object::print ); 956 | connect( foo, this, &bar_object::print_bar ); 957 | } 958 | 959 | void print( std::string_view str ) { m_print_called = str; } 960 | void print_bar() { m_print_bar_called = "bar"; } 961 | }; 962 | 963 | pg::subject< std::string > foo; 964 | 965 | { 966 | bar_object bar( foo, print_called, print_bar_called ); 967 | 968 | foo.notify( "foo" ); 969 | 970 | assert_true( print_called == "foo" ); 971 | assert_true( print_bar_called == "bar" ); 972 | } 973 | 974 | print_called.clear(); 975 | print_bar_called.clear(); 976 | 977 | foo.notify( "baz" ); 978 | 979 | assert_true( print_called.empty() ); 980 | assert_true( print_bar_called.empty() ); 981 | } 982 | #endif 983 | } 984 | 985 | int main( int /* argc */, char * /* argv */[] ) 986 | { 987 | free_function_observer(); 988 | lambda_function_observer(); 989 | std_function_observer(); 990 | functor_observer(); 991 | member_function_observer(); 992 | subject_subject_observer(); 993 | observer_owner_lifetime(); 994 | subject_lifetime(); 995 | scoped_observer(); 996 | observer_disconnect(); 997 | observer_notify_and_disconnect_order(); 998 | block_subject(); 999 | type_compatibility(); 1000 | invoke_function(); 1001 | const_and_forwarding(); 1002 | readme_examples(); 1003 | 1004 | std::cout << "Total asserts: " << total_asserts << ", asserts failed: " << failed_asserts << std::endl; 1005 | 1006 | return failed_asserts ? 1 : 0; 1007 | } 1008 | --------------------------------------------------------------------------------