Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Application of Rule 15.0.1 to Abstract Interface Classes
#1
It seems rule 15.0.1 has conflicting requirements for pure virtual / abstract classes with no data members. e.g. if we create the following classes:

Code:
class CanMessage {
public:
  CanMessage();

  // Required for inheritance
  virtual ~CanMessage() = default;

  // Want to move and copy through derived classes
  CanMessage(const CanMessage&) noexcept = default;
  CanMessage(CanMessage&&) noexcept = default;
  CanMessage& operator=(const CanMessage&) & noexcept = default;
  CanMessage& operator=(CanMessage&&) & noexcept = default;

  virtual uint32_t Id() = 0;
  virtual uint8_t DataLength() = 0;
  virtual std::span<std::byte> Data() = 0;
};

class CanClassicMessage : public CanMessage {
public:
  CanClassicMessage(uint32_t id, uint8_t data_length) : CanMessage{}, id_{id} data_length_{data_length} {}
  ~CanClassicMessage() noexcept override = default;

  CanClassicMessage(const CanClassicMessage&) noexcept = default;
  CanClassicMessage(CanClassicMessage&&) noexcept = default;
  CanClassicMessage& operator=(const CanClassicMessage&) & noexcept = default;
  CanClassicMessage& operator=(CanClassicMessage&&) & noexcept = default;

  uint32_t Id() override { return id_; }
  uint8_t DataLength() override { return data_length_; }
  std::span<std::byte> Data() { return data_; }
 
private:
  uint32_t id_;
  uint8_t data_length_;
  std::array<std::byte, 8> data_;
};

class CanFDMessage : public CanMessage {
public:
  CanFDMessage(uint32_t id, uint8_t data_length) : CanMessage{}, id_{id} data_length_{data_length} {}
  ~CanFDMessage() noexcept override = default;

  CanFDMessage(const CanFDMessage&) noexcept = default;
  CanFDMessage(CanFDMessage&&) noexcept = default;
  CanFDMessage& operator=(const CanFDMessage&) & noexcept = default;
  CanFDMessage& operator=(CanFDMessage&&) & noexcept = default;

  uint32_t Id() override { return id_; }
  uint8_t DataLength() override { return data_length_; }
  std::span<std::byte> Data() { return data_; }

private:
  uint32_t id_;
  uint8_t data_length_;
  std::array<std::byte, 64> data_;
}

Per the rule, we either need 
  • a protected non-virtual destructor.
  • Unmovable class that has a public virtual destructor.

However these prevent us from being able to create a std::unique_ptr to the base class (and allow the derived classes to manage their data through their destructors), or prevents us from being able to copy or move one CanClassicMessage to another CanClassicMessage, if the base copy/move constructors are deleted.

There is an exception for Aggreagate types, but it seems there needs to be an other one for an Interface/Abstract class which holds no data.
Reply
#2
We don't agree that your use case requires a modification of rule 15-0-1.

Your code shows one of the issues the rule is seeking to prevent:
A function receiving references to CanMessage objects as parameters could call one of the public copy or move operations.
This would not copy or move the leaf class (e.g. CanClassicMessage) objects, but only their base class part.
In your example no data would be copied or moved at all since CanMessage does not have any data members.

In order to avoid this slicing and make your code compliant, you need to make CanMessage unmovable.
You have already correctly observed that you must maintain the public virtual destructor for your use case.
Note that the rule does not require you to make the leaf classes unmovable.

Since your design makes use of the default copy and move operations in the leaf classes, you are implictly calling the respective operations in the base class. Therefore, you need to make the copy and move operations in CanMessage protected.
In the leaf classes, these operations are public and defaulted (as in your code), which is the same as not stating them at all (Rule of Zero).

Without guarantee of fitness for any purpose, a compliant (w.r.t. to 15.0.1) version of your example is

class CanMessage {
public:
  virtual ~CanMessage() = default;

protected:
  CanMessage() = default;

  // Want to move and copy through derived classes
  CanMessage(const CanMessage&) noexcept = default;
  CanMessage(CanMessage&&) noexcept = default;
  CanMessage& operator=(const CanMessage&) & noexcept = default;
  CanMessage& operator=(CanMessage&&) & noexcept = default;

public:
  virtual uint32_t Id() = 0;
  virtual uint8_t DataLength() = 0;
  virtual std:: span<std::byte> Data() = 0;
};

class CanClassicMessage : public CanMessage {
public:
  CanClassicMessage(uint32_t id, uint8_t data_length) : id_{id}, data_length_{data_length} {}

  uint32_t Id() override { return id_; }
  uint8_t DataLength() override { return data_length_; }
  std:: span<std::byte> Data() { return data_; }

private:
  uint32_t id_;
  uint8_t data_length_;
  std::array<std::byte, 8> data_;
};

class CanFDMessage : public CanMessage {
public:
  CanFDMessage(uint32_t id, uint8_t data_length) : id_{id}, data_length_{data_length} {}

  uint32_t Id() override { return id_; }
  uint8_t DataLength() override { return data_length_; }
  std:: span<std::byte> Data() { return data_; }

private:
  uint32_t id_;
  uint8_t data_length_;
  std::array<std::byte, 64> data_;
};

// Test

static_assert(!std::is_copy_constructible_v<CanMessage>);

static_assert(!std::is_copy_assignable_v<CanMessage>);

static_assert(!std::is_move_constructible_v<CanMessage>);

static_assert(!std::is_move_assignable_v<CanMessage>);




static_assert(std::is_copy_constructible_v<CanClassicMessage>);

static_assert(std::is_copy_assignable_v<CanClassicMessage>);

static_assert(std::is_move_constructible_v<CanClassicMessage>);

static_assert(std::is_move_assignable_v<CanClassicMessage>);




static_assert(std::is_copy_constructible_v<CanFDMessage>);

static_assert(std::is_copy_assignable_v<CanFDMessage>);

static_assert(std::is_move_constructible_v<CanFDMessage>);

static_assert(std::is_move_assignable_v<CanFDMessage>);
Posted by and on behalf of
the MISRA C++ Working Group
Reply
#3
Thank you for the thorough explanation and writeup!
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)