-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
209588c
commit 895eae2
Showing
9 changed files
with
296 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters