Skip to content

Commit 895eae2

Browse files
committed
ups
1 parent 209588c commit 895eae2

9 files changed

+296
-47
lines changed

CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,4 +409,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
409409

410410
add_executable(fork src/fork.cpp)
411411

412-
endif()
412+
endif()
413+
414+
add_executable(any src/any.cpp)
415+
416+
add_executable(copy_and_swap src/copy_and_swap.cpp)

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ read more [here](https://ros-developer.com/2017/11/08/docker/)
103103
## [C++ Tutorials](#)
104104
* [Align](docs/align.md)
105105
* [Allocator](docs/allocator.md)
106-
* [Algorithms Library](docs/algorithms.md)
106+
* [Algorithms Library](docs/algorithms.md)
107+
* [Any](docs/any.md)
107108
* [Assert](docs/assert.md)
108109
* [Atomic operations and Atomic Types](docs/atomic.md)
109110
* [Asynchronous programming](docs/asynchronous_programming.md)

docs/any.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# std::any
2+
`std::any` is a feature introduced in C++17. It provides a type-safe container for single values of any type.
3+
4+
## Explanation
5+
- `std::any` can store an instance of any type that satisfies `CopyConstructible`.
6+
- It's a better, type-safe alternative to `void*` for storing values of unknown type at compile time.
7+
- `std::any_cast` is a function template in C++ that is used to retrieve the value stored in an `std::any` object. It serves two primary purposes: to safely extract a value of a specific type from an `std::any` object and to check the type of the stored value at runtime.
8+
- If the cast to the original type fails (i.e., if you try to cast it to a different type), an exception of type `std::bad_any_cast` is thrown.
9+
10+
## Example
11+
12+
```cpp
13+
#include <iostream>
14+
#include <any>
15+
#include <string>
16+
17+
int main() {
18+
std::any a = 1; // Storing an int
19+
std::cout << std::any_cast<int>(a) << '\n'; // Casting back to int
20+
21+
a = 3.14; // Now storing a double
22+
std::cout << std::any_cast<double>(a) << '\n'; // Casting back to double
23+
24+
a = std::string("Hello, std::any!");
25+
std::cout << std::any_cast<std::string>(a) << '\n'; // Casting back to std::string
26+
27+
// Attempting to cast to an incorrect type will throw std::bad_any_cast
28+
try {
29+
std::cout << std::any_cast<int>(a) << '\n';
30+
} catch (const std::bad_any_cast& e) {
31+
std::cout << e.what() << '\n';
32+
}
33+
}
34+
```
35+
36+
In this example:
37+
- `std::any a` is first assigned an integer, then a double, and finally a `std::string`.
38+
- For each assignment, it's cast back to its original type using `std::any_cast`.
39+
- The last block demonstrates the exception handling in case of a wrong cast. When we try to cast the `std::string` to `int`, `std::bad_any_cast` is thrown.
40+
41+
## std::any with and without satisfying the CopyConstructible condition
42+
43+
To illustrate a non-`CopyConstructible` type, we can create a class that explicitly deletes its copy constructor. This class can't be used with `std::any` directly, as it violates the `CopyConstructible` requirement.
44+
45+
```cpp
46+
#include <iostream>
47+
#include <any>
48+
#include <string>
49+
50+
class NonCopyable {
51+
public:
52+
NonCopyable() = default;
53+
NonCopyable(const NonCopyable&) = delete; // deleting the copy constructor
54+
NonCopyable& operator=(const NonCopyable&) = delete;
55+
};
56+
57+
int main() {
58+
NonCopyable nonCopyableObj;
59+
60+
// This line will cause a compilation error because NonCopyable is not CopyConstructible
61+
// std::any a = nonCopyableObj;
62+
63+
// Uncommenting the above line will lead to a compilation error
64+
// Instead, you can store a pointer to the object
65+
std::any a = &nonCopyableObj; // Storing a pointer is fine
66+
67+
// Retrieving the pointer back
68+
auto storedObj = std::any_cast<NonCopyable*>(a);
69+
// Use storedObj as a pointer to NonCopyable
70+
}
71+
```
72+
73+
In the second example, attempting to store an instance of `NonCopyable` in `std::any` will result in a compilation error. However, storing a pointer to a `NonCopyable` object works because pointers are `CopyConstructible`.
74+
75+
76+
## How to find out if a class is CopyConstructible
77+
78+
### 1. Check the Source Code
79+
A class is `CopyConstructible` if it has a public copy constructor. Look for something like:
80+
```cpp
81+
ClassName(const ClassName&);
82+
```
83+
If this constructor is public and not deleted, the class is `CopyConstructible`.
84+
85+
### 2. Use Type Traits
86+
C++ provides type traits in the `<type_traits>` header that can be used to check certain properties at compile-time. You can use `std::is_copy_constructible` to check if a type is `CopyConstructible`. For example:
87+
```cpp
88+
#include <type_traits>
89+
90+
if constexpr (std::is_copy_constructible<ThirdPartyClass>::value) {
91+
// The class is CopyConstructible
92+
} else {
93+
// The class is not CopyConstructible
94+
}
95+
```
96+
This method is useful when you want to write code that depends on whether a type is `CopyConstructible` or not.
97+
98+
### 3. Attempt Compilation
99+
As a quick-and-dirty method, you can write a small piece of code that attempts to copy an instance of the class. If the code fails to compile, it's likely that the class is not `CopyConstructible`. However, this method is less precise and should be used cautiously.

docs/copy-and-swap_idiom.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,70 @@
11

2+
```cpp
3+
struct S {
4+
size_t m_size = 0;
5+
int *m_data;
6+
7+
S(size_t size = 0) : m_size(size) {
8+
std::cout << "ctor: " << m_size << std::endl;
9+
m_data = new int[m_size];
10+
}
11+
12+
~S() { delete[] m_data; }
13+
14+
// copy constructor
15+
S(const S &rhs) : m_size(rhs.m_size), m_data(new int[m_size]) {
16+
for (std::size_t i = 0; i < rhs.m_size; i++) {
17+
m_data[i] = rhs.m_data[i];
18+
}
19+
}
20+
21+
// Copy assignment operator
22+
S &operator=(S rhs) // Note: pass by value to handle self-assignment and for
23+
// exception safety
24+
25+
{
26+
std::swap(m_size, rhs.m_size);
27+
std::swap(m_data, rhs.m_data);
28+
return *this;
29+
}
30+
31+
// Traditional Copy Assignment Operator
32+
S &operator=(const S &rhs) {
33+
std::cout << "Copy assignment operator" << std::endl;
34+
if (this != &rhs) { // Check for self-assignment
35+
delete[] m_data; // Free existing resource
36+
37+
m_size = rhs.m_size;
38+
m_data = new int[m_size]; // Allocate new resource
39+
for (std::size_t i = 0; i < m_size; i++) {
40+
m_data[i] = rhs.m_data[i]; // Copy the resource data
41+
}
42+
}
43+
return *this; // Return the current object
44+
}
45+
};
46+
```
47+
48+
1. **Traditional Copy Assignment Signature (`S& operator=(const S& rhs)`):**
49+
- Signature: `S& operator=(const S& rhs)`
50+
- This version takes a constant reference to the source object.
51+
- This is the conventional form for the copy assignment operator. It takes a constant reference to the source object. This method involves checking for self-assignment, dealing with resource allocation, and ensuring proper copying of the object's contents.
52+
53+
54+
2. **Copy-and-Swap Assignment Signature (`S& operator=(S rhs)`):**
55+
- Signature: `S& operator=(S rhs)`
56+
- This version takes the source object by value.
57+
- This form is part of the copy-and-swap idiom, which is a modern and exception-safe approach to implementing assignment operators in C++. Here, the parameter is passed by value (`S rhs`), not by constant reference.
58+
- The key advantage of this approach is its simplicity and exception safety. When you pass by value, the copy constructor of `S` is called to create the `rhs` object. If this copy constructor throws an exception (for example, during memory allocation), it will do so before entering the body of the assignment operator, thus preventing any changes to the current object and maintaining strong exception safety.
59+
- Inside the function, `std::swap` is used to swap the internals of the current object with those of `rhs`. When `rhs` goes out of scope at the end of the function, it automatically cleans up the old resources, as it now holds the old state of the current object.
60+
61+
In the specific implementation I provided, the `S& operator=(S rhs)` signature is used for the copy-and-swap idiom. This is a deliberate choice to simplify resource management and improve exception safety, but it does differ from the traditional copy assignment signature. Both approaches are valid, but they serve different purposes and have different implications for resource management and exception handling.
62+
63+
The traditional approach is more straightforward and direct, whereas the copy-and-swap idiom provides strong exception safety and simplifies code, especially for classes that manage complex resources. The choice between the two depends on the specific requirements and design goals of your class.
64+
65+
66+
In practice, you would typically choose one of these strategies based on your class's design and resource management needs. For example, if your class manages resources and you want to ensure strong exception safety, you might prefer the copy-and-swap idiom. If your class is simpler or if performance considerations outweigh the benefits of exception safety, you might choose the traditional copy assignment operator.
67+
68+
69+
270

3-
Refs: [1](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom)

docs/type_traits.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,58 @@ In this example:
141141

142142
This approach ensures that the container is memory safe without imposing unnecessary overhead on non-pointer types. It's a common pattern in generic programming where the behavior needs to be adjusted based on type properties. Type traits make this kind of template metaprogramming possible and efficient in C++.
143143

144+
145+
146+
## std::is_copy_constructible` and `std::is_copy_constructible_v
147+
148+
`std::is_copy_constructible` and `std::is_copy_constructible_v` are related but have slightly different usages in the context of C++ type traits. Here's an explanation of each and how they are used:
149+
150+
### `std::is_copy_constructible`
151+
- `std::is_copy_constructible` is a type trait that is a part of the C++ Standard Library's `<type_traits>` header.
152+
- It is a template that takes a type as its template parameter and evaluates to a `std::integral_constant` (essentially a compile-time constant).
153+
- The `value` member of this `integral_constant` will be `true` if the type is copy constructible, and `false` otherwise.
154+
155+
Example usage:
156+
```cpp
157+
if (std::is_copy_constructible<MyClass>::value) {
158+
// MyClass is copy constructible
159+
} else {
160+
// MyClass is not copy constructible
161+
}
162+
```
163+
164+
### `std::is_copy_constructible_v`
165+
- `std::is_copy_constructible_v` is a shorthand (introduced in C++17) for `std::is_copy_constructible<T>::value`.
166+
- It's a template variable, not a type, and directly provides a `bool` value.
167+
- It simplifies the syntax when you just need the boolean result and don't need the full `integral_constant` type.
168+
169+
Example usage:
170+
```cpp
171+
if (std::is_copy_constructible_v<MyClass>) {
172+
// MyClass is copy constructible
173+
} else {
174+
// MyClass is not copy constructible
175+
}
176+
```
177+
178+
### Passing an Object Instance
179+
It's important to note that both `std::is_copy_constructible` and `std::is_copy_constructible_v` take a type as a template argument, not an object instance. So, you would pass the type of the object, not the object itself. For example, if you have an object `myObject` of type `MyClass`, you would check `std::is_copy_constructible_v<MyClass>`.
180+
181+
Incorrect usage:
182+
```cpp
183+
// This is incorrect and will not compile
184+
if (std::is_copy_constructible_v<myObject>) {
185+
// ...
186+
}
187+
```
188+
189+
Correct usage:
190+
```cpp
191+
// This is the correct way to use it
192+
if (std::is_copy_constructible_v<MyClass>) {
193+
// ...
194+
}
195+
```
196+
197+
144198
[code](../src/type_traits.cpp)

src/any.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <algorithm>
2+
#include <any>
3+
#include <iostream>
4+
#include <type_traits>
5+
6+
int main() {
7+
std::any a;
8+
9+
a = 1;
10+
11+
std::cout << std::any_cast<int>(a) << std::endl;
12+
13+
// std::is_integral_v<int>;
14+
}

src/copy_and_swap.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include <iostream>
2+
3+
struct S {
4+
size_t m_size = 0;
5+
int *m_data;
6+
7+
S(size_t size = 0) : m_size(size) {
8+
std::cout << "ctor: " << m_size << std::endl;
9+
m_data = new int[m_size];
10+
}
11+
12+
~S() { delete[] m_data; }
13+
14+
// copy constructor
15+
S(const S &rhs) : m_size(rhs.m_size), m_data(new int[m_size]) {
16+
for (std::size_t i = 0; i < rhs.m_size; i++) {
17+
m_data[i] = rhs.m_data[i];
18+
}
19+
}
20+
21+
// Copy assignment operator
22+
S &operator=(S rhs) // Note: pass by value to handle self-assignment and for
23+
// exception safety
24+
25+
{
26+
std::swap(m_size, rhs.m_size);
27+
std::swap(m_data, rhs.m_data);
28+
return *this;
29+
}
30+
31+
// Traditional Copy Assignment Operator
32+
S &operator=(const S &rhs) {
33+
std::cout << "Copy assignment operator" << std::endl;
34+
if (this != &rhs) { // Check for self-assignment
35+
delete[] m_data; // Free existing resource
36+
37+
m_size = rhs.m_size;
38+
m_data = new int[m_size]; // Allocate new resource
39+
for (std::size_t i = 0; i < m_size; i++) {
40+
m_data[i] = rhs.m_data[i]; // Copy the resource data
41+
}
42+
}
43+
return *this; // Return the current object
44+
}
45+
};
46+
47+
int main() {}

src/main.cpp

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,6 @@
44
#include <iostream>
55
#include <memory>
66

7-
struct Foo {
8-
std::string id;
9-
Foo(std::string id) : id(id) {
10-
11-
std::cout << id << " constructor" << std::endl;
12-
}
13-
~Foo() { std::cout << id << " destructor" << std::endl; }
14-
};
15-
16-
Foo globalFoo("global object");
17-
static Foo staticFoo("static object");
18-
19-
void runBeforeTermination() {
20-
std::cout << "cleaning up temp dir before termination..." << std::endl;
21-
}
22-
void abortExample() {
23-
Foo fooObject("abortExample");
24-
std::shared_ptr<Foo> fooObject_ptr(new Foo("abortExample_ptr"));
25-
atexit(runBeforeTermination);
26-
abort();
27-
}
28-
297
int main() {
308
int array[5] = {0, 1, 2, 3, 4};
319

@@ -41,12 +19,4 @@ int main() {
4119
i++) { // This loop intentionally goes well beyond the array's bounds
4220
array[i] = i;
4321
}
44-
45-
// std::cout << "\nReading values after overflow:\n";
46-
// // Reading values after overflow
47-
// for (int i = 0; i < int(100);
48-
// i++) { // Again, intentionally reading beyond the array's bounds
49-
// std::cout << "array[" << i << "] = " << array[i] << "\n";
50-
// std::cout << "i = " << i << "\n";
51-
// }
5222
}

src/signals.cpp

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,22 @@
33
#include <iostream>
44
#include <unistd.h> // getpid()
55

6-
void SIGSEGV_Handler(int signum) {
7-
std::cout << "oh my god! segmenation fault happened" << std::endl;
8-
printf("Process %d got signal %d\n", getpid(), signum);
9-
// kill(getpid(), signum);
10-
exit(signum);
11-
}
12-
136
void SIGINT_Handler(int signum) {
147
std::cout << "Interrupt signal (" << signum << ") received.\n";
158
// Cleanup and close up logic
16-
exit(signum);
9+
// exit(signum);
1710
}
1811

1912
sig_atomic_t s_value = 0;
2013
void SIGTERM_Handler(int signum) { s_value = signum; }
2114

15+
void SIGSEGV_Handler(int signum) {
16+
std::cout << "oh my god! segmenation fault happened" << std::endl;
17+
printf("Process %d got signal %d\n", getpid(), signum);
18+
// kill(getpid(), signum);
19+
exit(signum);
20+
}
2221
int main() {
23-
// signal(SIGSEGV, SIGSEGV_Handler);
24-
// int *p;
25-
// *p = 10;
26-
27-
// // Register signal SIGINT and signal handler
28-
// signal(SIGINT, SIGINT_Handler);
2922

3023
signal(SIGTERM, SIGTERM_Handler);
3124
std::cout << "Before called Signal = " << s_value << std::endl;

0 commit comments

Comments
 (0)