Skip to content

Commit

Permalink
Merge pull request #2115 from Dennis40816/iox-2113-move-helper-to-design
Browse files Browse the repository at this point in the history
Move `move_and_copy_helper` to `iceoryx/design`
  • Loading branch information
elBoberido authored Dec 6, 2023
2 parents efe0200 + 4af185b commit 6303f2e
Show file tree
Hide file tree
Showing 9 changed files with 664 additions and 148 deletions.
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ necessary, do the following:
1. Contact the maintainers beforehand by opening an issue to discuss the necessity
1. If accepted, add the new header to `tools/scripts/used-headers.txt` for the CI to pass

### Eliminating code duplication

1. In some cases, the code in the constructor and assignment operations may be largely duplicated. It is encouraged to move this duplicated code into a separate function (e.g., `copy_and_move_impl`) for better reuse. Additionally, the `MoveAndCopyHelper` in `iceoryx/design` (refer to [MoveAndCopyHelper](./doc/design/move_and_copy_helper.md)) offers some functionalities that make this process easier.

## Folder structure

The folder structure boils down to:
Expand Down
152 changes: 152 additions & 0 deletions doc/design/move_and_copy_helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Move And Copy Helper

## Summary and problem description

Currently, some classes in our codebase use assignment operator in the constructor (ctor) to avoid code duplication, as seen in issue [#1517](https://github.com/eclipse-iceoryx/iceoryx/issues/1517) . However, it's noted in issue [#1686](https://github.com/eclipse-iceoryx/iceoryx/issues/1686) that self-assignment should not happen in the ctor. Therefore, an alternative approach needs to be adopted to replace the current implementation.

One method involves placing the shared logical code within a separate function, hereafter referred to as `copy_and_move_impl`. This function is then called within the ctor and assignment operations, using template parameters to specify the method of member initialization.

## Premise

When a class performs highly consistent tasks during construction (ctor) and assignment, implementers often extract the repetitive parts into a separate `copy_and_move_impl`. However, there are inevitably minor differences between move, copy, and ctor, assignment (for example, during ctor, specific variables need to be initialized to avoid garbage values affecting subsequent behaviors, etc...). In these cases, the `MoveAndCopyHelper` can be used to simplify the code.

## Terminology

- `MoveAndCopyOperations`: an enum class, including

- `CopyConstructor`
- `MoveConstructor`
- `CopyAssignment`
- `MoveAssignment`

## Goals

The purpose of the `MoveAndCopyHelper` is:
- To allow special constructors to indicate the source of the call (e.g., CopyConstructor) when calling `copy_and_move_impl`.
- To provide a consistent way of expressing data exchange in `copy_and_move_impl`.
- To offer a way to ignore `MoveAndCopyOperations` and forcibly use ctor or assignment, facilitating users' needs in special circumstances.

## Design

- Provide the enum class `MoveAndCopyOperations` to describe the expected behavior of member variables during initialization (e.g., using copy or move). Passing this enum as a template parameter to `copy_and_move_impl` ensures whether special constructors are called as expected.
- Provide functions like `transfer` to ensure that the exchange process of member variables conforms to `MoveAndCopyOperations`.

### Scenario

#### Transfer
Presently, there are three functions: `transfer`, `create_new`, and `assign`. The scenarios outlined below briefly describe the appropriate usage for each function.

- `transfer`: This is a general-purpose function for data transfer. It is recommended for use in cases where you are not dealing with potentially uninitialized memory.
- `create_new`: In certain instances, particularly during constructor operations, some class members are lazy initialization. For these members, the copy or move constructor must be employed, even when they are being accessed via the assignment operator.
- `assign`: TBD.

### Code example

```cpp
// header

#include "iox/move_and_copy_helper.hpp"

constexpr uint64_t CAPACITY {1024};

class Test
{
public:
Test();
Test(const Test&);
Test(Test&&);
Test& operator=(const Test&);
Test& operator=(Test&&);

uint64_t size();

private:
int m_data[CAPACITY];
uint64_t m_size{0};
}

// implementation
Test::Test(const Test& rhs)
{
copy_and_move_impl<MoveAndCopyOperations::CopyConstructor>(rhs);
}

Test::Test(Test&& rhs)
{
copy_and_move_impl<MoveAndCopyOperations::MoveConstructor>(rhs);
}

Test& Test::operator=(const Test& rhs)
{
// you may do self-assignment check in copy_and_move_impl
if (this != &rhs)
{
copy_and_move_impl<MoveAndCopyOperations::CopyAssignment>(rhs);
}

return *this;
}

Test& Test::operator=(Test&& rhs)
{
// you may do self-assignment check in copy_and_move_impl
if (this != &rhs)
{
copy_and_move_impl<MoveAndCopyOperations::MoveAssignment>(std::move(rhs));
}
return *this;
}

void Test::size()
{
return m_size;
}

template <MoveAndCopyOperations Opt, typename RhsType>
void Test::copy_and_move_impl(RhsType&& rhs)
{
// you can set alias to simplify code
using Helper = MoveAndCopyHelper<Opt>;

// you can determine the current 'Opt' at compile time for compile-time branching decisions.
constexpr bool is_ctor = Helper::is_ctor;
constexpr bool is_move = Helper::is_move;

// // self assignment check if needed
// if constexpr (!is_ctor)
// {
// if (this == &rhs)
// {
// return;
// }
// }

// for ctor operation
if constexpr (is_ctor)
{
// reset something if needed
reset_something();
}

// transfer data example
for (uint64_t i = 0; i < rhs.size(); ++i)
{
// @Recommended method
Helper::transfer(m_data[i], Helper::move_or_copy(rhs.data[i]));

// @Alternative method
// if constexpr (is_move)
// {
// Helper::transfer(m_data[i], std::move(rhs.data[i]));
// }
// else
// {
// Helper::transfer(m_data[i], rhs.data[i]);
// }
}
}
```
For more examples, see
- `iceoryx_dust/container/detail/fixed_position_container.inl`
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#ifndef IOX_DUST_CONTAINER_DETAIL_FIXED_POSITION_CONTAINER_INL
#define IOX_DUST_CONTAINER_DETAIL_FIXED_POSITION_CONTAINER_INL

#include "iox/detail/fixed_position_container_helper.hpp"
#include "iox/fixed_position_container.hpp"

namespace iox
Expand Down Expand Up @@ -56,13 +55,13 @@ inline FixedPositionContainer<T, CAPACITY>::~FixedPositionContainer() noexcept
template <typename T, uint64_t CAPACITY>
inline FixedPositionContainer<T, CAPACITY>::FixedPositionContainer(const FixedPositionContainer& rhs) noexcept
{
copy_and_move_impl<detail::MoveAndCopyOperations::CopyConstructor>(rhs);
copy_and_move_impl<MoveAndCopyOperations::CopyConstructor>(rhs);
}

template <typename T, uint64_t CAPACITY>
inline FixedPositionContainer<T, CAPACITY>::FixedPositionContainer(FixedPositionContainer&& rhs) noexcept
{
copy_and_move_impl<detail::MoveAndCopyOperations::MoveConstructor>(std::move(rhs));
copy_and_move_impl<MoveAndCopyOperations::MoveConstructor>(std::move(rhs));
}

template <typename T, uint64_t CAPACITY>
Expand All @@ -71,7 +70,7 @@ FixedPositionContainer<T, CAPACITY>::operator=(const FixedPositionContainer& rhs
{
if (this != &rhs)
{
copy_and_move_impl<detail::MoveAndCopyOperations::CopyAssignment>(rhs);
copy_and_move_impl<MoveAndCopyOperations::CopyAssignment>(rhs);
}
return *this;
}
Expand All @@ -82,20 +81,20 @@ FixedPositionContainer<T, CAPACITY>::operator=(FixedPositionContainer&& rhs) noe
{
if (this != &rhs)
{
copy_and_move_impl<detail::MoveAndCopyOperations::MoveAssignment>(std::move(rhs));
copy_and_move_impl<MoveAndCopyOperations::MoveAssignment>(std::move(rhs));
}
return *this;
}

template <typename T, uint64_t CAPACITY>
template <detail::MoveAndCopyOperations Opt, typename RhsType>
template <MoveAndCopyOperations Opt, typename RhsType>
inline void FixedPositionContainer<T, CAPACITY>::copy_and_move_impl(RhsType&& rhs) noexcept
{
// alias helper struct
using Helper = detail::MoveAndCopyHelper<Opt>;
using Helper = MoveAndCopyHelper<Opt>;

constexpr bool is_ctor = Helper::is_ctor();
constexpr bool is_move = Helper::is_move();
constexpr bool is_ctor = Helper::is_ctor;
constexpr bool is_move = Helper::is_move;

// status array is not yet initialized for constructor creation
if constexpr (is_ctor)
Expand All @@ -115,27 +114,13 @@ inline void FixedPositionContainer<T, CAPACITY>::copy_and_move_impl(RhsType&& rh
{
// When the slot is in the 'USED' state, it is safe to proceed with either construction (ctor) or assignment
// operation. Therefore, creation can be carried out according to the option specified by Opt.
if constexpr (is_move)
{
Helper::transfer(m_data[i], std::move(*rhs_it));
}
else
{
Helper::transfer(m_data[i], *rhs_it);
}
Helper::transfer(m_data[i], Helper::move_or_copy_it(rhs_it));
}
else
{
// When the slot is in the 'FREE' state, it is unsafe to proceed with assignment operation.
// Therefore, we need to force helper to use ctor create to make sure that the 'FREE' slots get initialized.
if constexpr (is_move)
{
Helper::ctor_create(m_data[i], std::move(*rhs_it));
}
else
{
Helper::ctor_create(m_data[i], *rhs_it);
}
Helper::create_new(m_data[i], Helper::move_or_copy_it(rhs_it));
}

m_status[i] = SlotStatus::USED;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

#include "iceoryx_hoofs/cxx/requires.hpp"
#include "iox/algorithm.hpp"
#include "iox/detail/fixed_position_container_helper.hpp"
#include "iox/move_and_copy_helper.hpp"
#include "iox/uninitialized_array.hpp"

#include <cstdint>
Expand Down Expand Up @@ -316,7 +316,7 @@ class FixedPositionContainer final
};

private:
template <detail::MoveAndCopyOperations Opt, typename RhsType>
template <MoveAndCopyOperations Opt, typename RhsType>
void copy_and_move_impl(RhsType&& rhs) noexcept;

private:
Expand Down
Loading

0 comments on commit 6303f2e

Please sign in to comment.