Skip to content

Commit

Permalink
ups
Browse files Browse the repository at this point in the history
  • Loading branch information
behnamasadi committed Jan 4, 2024
1 parent 209588c commit 895eae2
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 47 deletions.
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -409,4 +409,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")

add_executable(fork src/fork.cpp)

endif()
endif()

add_executable(any src/any.cpp)

add_executable(copy_and_swap src/copy_and_swap.cpp)
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ read more [here](https://ros-developer.com/2017/11/08/docker/)
## [C++ Tutorials](#)
* [Align](docs/align.md)
* [Allocator](docs/allocator.md)
* [Algorithms Library](docs/algorithms.md)
* [Algorithms Library](docs/algorithms.md)
* [Any](docs/any.md)
* [Assert](docs/assert.md)
* [Atomic operations and Atomic Types](docs/atomic.md)
* [Asynchronous programming](docs/asynchronous_programming.md)
Expand Down
99 changes: 99 additions & 0 deletions docs/any.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# std::any
`std::any` is a feature introduced in C++17. It provides a type-safe container for single values of any type.

## Explanation
- `std::any` can store an instance of any type that satisfies `CopyConstructible`.
- It's a better, type-safe alternative to `void*` for storing values of unknown type at compile time.
- `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.
- 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.

## Example

```cpp
#include <iostream>
#include <any>
#include <string>

int main() {
std::any a = 1; // Storing an int
std::cout << std::any_cast<int>(a) << '\n'; // Casting back to int

a = 3.14; // Now storing a double
std::cout << std::any_cast<double>(a) << '\n'; // Casting back to double

a = std::string("Hello, std::any!");
std::cout << std::any_cast<std::string>(a) << '\n'; // Casting back to std::string

// Attempting to cast to an incorrect type will throw std::bad_any_cast
try {
std::cout << std::any_cast<int>(a) << '\n';
} catch (const std::bad_any_cast& e) {
std::cout << e.what() << '\n';
}
}
```

In this example:
- `std::any a` is first assigned an integer, then a double, and finally a `std::string`.
- For each assignment, it's cast back to its original type using `std::any_cast`.
- 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.

## std::any with and without satisfying the CopyConstructible condition

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.

```cpp
#include <iostream>
#include <any>
#include <string>

class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // deleting the copy constructor
NonCopyable& operator=(const NonCopyable&) = delete;
};

int main() {
NonCopyable nonCopyableObj;

// This line will cause a compilation error because NonCopyable is not CopyConstructible
// std::any a = nonCopyableObj;

// Uncommenting the above line will lead to a compilation error
// Instead, you can store a pointer to the object
std::any a = &nonCopyableObj; // Storing a pointer is fine

// Retrieving the pointer back
auto storedObj = std::any_cast<NonCopyable*>(a);
// Use storedObj as a pointer to NonCopyable
}
```
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`.
## How to find out if a class is CopyConstructible
### 1. Check the Source Code
A class is `CopyConstructible` if it has a public copy constructor. Look for something like:
```cpp
ClassName(const ClassName&);
```
If this constructor is public and not deleted, the class is `CopyConstructible`.

### 2. Use Type Traits
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:
```cpp
#include <type_traits>

if constexpr (std::is_copy_constructible<ThirdPartyClass>::value) {
// The class is CopyConstructible
} else {
// The class is not CopyConstructible
}
```
This method is useful when you want to write code that depends on whether a type is `CopyConstructible` or not.

### 3. Attempt Compilation
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.
69 changes: 68 additions & 1 deletion docs/copy-and-swap_idiom.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@

```cpp
struct S {
size_t m_size = 0;
int *m_data;

S(size_t size = 0) : m_size(size) {
std::cout << "ctor: " << m_size << std::endl;
m_data = new int[m_size];
}

~S() { delete[] m_data; }

// copy constructor
S(const S &rhs) : m_size(rhs.m_size), m_data(new int[m_size]) {
for (std::size_t i = 0; i < rhs.m_size; i++) {
m_data[i] = rhs.m_data[i];
}
}

// Copy assignment operator
S &operator=(S rhs) // Note: pass by value to handle self-assignment and for
// exception safety

{
std::swap(m_size, rhs.m_size);
std::swap(m_data, rhs.m_data);
return *this;
}

// Traditional Copy Assignment Operator
S &operator=(const S &rhs) {
std::cout << "Copy assignment operator" << std::endl;
if (this != &rhs) { // Check for self-assignment
delete[] m_data; // Free existing resource

m_size = rhs.m_size;
m_data = new int[m_size]; // Allocate new resource
for (std::size_t i = 0; i < m_size; i++) {
m_data[i] = rhs.m_data[i]; // Copy the resource data
}
}
return *this; // Return the current object
}
};
```

1. **Traditional Copy Assignment Signature (`S& operator=(const S& rhs)`):**
- Signature: `S& operator=(const S& rhs)`
- This version takes a constant reference to the source object.
- 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.


2. **Copy-and-Swap Assignment Signature (`S& operator=(S rhs)`):**
- Signature: `S& operator=(S rhs)`
- This version takes the source object by value.
- 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.
- 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.
- 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.

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.

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.


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.




Refs: [1](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom)
54 changes: 54 additions & 0 deletions docs/type_traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,58 @@ In this example:

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++.



## std::is_copy_constructible` and `std::is_copy_constructible_v

`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:

### `std::is_copy_constructible`
- `std::is_copy_constructible` is a type trait that is a part of the C++ Standard Library's `<type_traits>` header.
- It is a template that takes a type as its template parameter and evaluates to a `std::integral_constant` (essentially a compile-time constant).
- The `value` member of this `integral_constant` will be `true` if the type is copy constructible, and `false` otherwise.

Example usage:
```cpp
if (std::is_copy_constructible<MyClass>::value) {
// MyClass is copy constructible
} else {
// MyClass is not copy constructible
}
```

### `std::is_copy_constructible_v`
- `std::is_copy_constructible_v` is a shorthand (introduced in C++17) for `std::is_copy_constructible<T>::value`.
- It's a template variable, not a type, and directly provides a `bool` value.
- It simplifies the syntax when you just need the boolean result and don't need the full `integral_constant` type.

Example usage:
```cpp
if (std::is_copy_constructible_v<MyClass>) {
// MyClass is copy constructible
} else {
// MyClass is not copy constructible
}
```

### Passing an Object Instance
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>`.

Incorrect usage:
```cpp
// This is incorrect and will not compile
if (std::is_copy_constructible_v<myObject>) {
// ...
}
```

Correct usage:
```cpp
// This is the correct way to use it
if (std::is_copy_constructible_v<MyClass>) {
// ...
}
```


[code](../src/type_traits.cpp)
14 changes: 14 additions & 0 deletions src/any.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <algorithm>
#include <any>
#include <iostream>
#include <type_traits>

int main() {
std::any a;

a = 1;

std::cout << std::any_cast<int>(a) << std::endl;

// std::is_integral_v<int>;
}
47 changes: 47 additions & 0 deletions src/copy_and_swap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <iostream>

struct S {
size_t m_size = 0;
int *m_data;

S(size_t size = 0) : m_size(size) {
std::cout << "ctor: " << m_size << std::endl;
m_data = new int[m_size];
}

~S() { delete[] m_data; }

// copy constructor
S(const S &rhs) : m_size(rhs.m_size), m_data(new int[m_size]) {
for (std::size_t i = 0; i < rhs.m_size; i++) {
m_data[i] = rhs.m_data[i];
}
}

// Copy assignment operator
S &operator=(S rhs) // Note: pass by value to handle self-assignment and for
// exception safety

{
std::swap(m_size, rhs.m_size);
std::swap(m_data, rhs.m_data);
return *this;
}

// Traditional Copy Assignment Operator
S &operator=(const S &rhs) {
std::cout << "Copy assignment operator" << std::endl;
if (this != &rhs) { // Check for self-assignment
delete[] m_data; // Free existing resource

m_size = rhs.m_size;
m_data = new int[m_size]; // Allocate new resource
for (std::size_t i = 0; i < m_size; i++) {
m_data[i] = rhs.m_data[i]; // Copy the resource data
}
}
return *this; // Return the current object
}
};

int main() {}
30 changes: 0 additions & 30 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,6 @@
#include <iostream>
#include <memory>

struct Foo {
std::string id;
Foo(std::string id) : id(id) {

std::cout << id << " constructor" << std::endl;
}
~Foo() { std::cout << id << " destructor" << std::endl; }
};

Foo globalFoo("global object");
static Foo staticFoo("static object");

void runBeforeTermination() {
std::cout << "cleaning up temp dir before termination..." << std::endl;
}
void abortExample() {
Foo fooObject("abortExample");
std::shared_ptr<Foo> fooObject_ptr(new Foo("abortExample_ptr"));
atexit(runBeforeTermination);
abort();
}

int main() {
int array[5] = {0, 1, 2, 3, 4};

Expand All @@ -41,12 +19,4 @@ int main() {
i++) { // This loop intentionally goes well beyond the array's bounds
array[i] = i;
}

// std::cout << "\nReading values after overflow:\n";
// // Reading values after overflow
// for (int i = 0; i < int(100);
// i++) { // Again, intentionally reading beyond the array's bounds
// std::cout << "array[" << i << "] = " << array[i] << "\n";
// std::cout << "i = " << i << "\n";
// }
}
21 changes: 7 additions & 14 deletions src/signals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,22 @@
#include <iostream>
#include <unistd.h> // getpid()

void SIGSEGV_Handler(int signum) {
std::cout << "oh my god! segmenation fault happened" << std::endl;
printf("Process %d got signal %d\n", getpid(), signum);
// kill(getpid(), signum);
exit(signum);
}

void SIGINT_Handler(int signum) {
std::cout << "Interrupt signal (" << signum << ") received.\n";
// Cleanup and close up logic
exit(signum);
// exit(signum);
}

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

void SIGSEGV_Handler(int signum) {
std::cout << "oh my god! segmenation fault happened" << std::endl;
printf("Process %d got signal %d\n", getpid(), signum);
// kill(getpid(), signum);
exit(signum);
}
int main() {
// signal(SIGSEGV, SIGSEGV_Handler);
// int *p;
// *p = 10;

// // Register signal SIGINT and signal handler
// signal(SIGINT, SIGINT_Handler);

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

0 comments on commit 895eae2

Please sign in to comment.