为什么这个返回语句似乎需要一个const_cast?
我有一个 Chess 类,用来把棋盘上格子的状态编码成4 位整数。为了把这点抽象出来,我正在做我认为是标准做法的那种:创建一个能够模拟引用的类,以便能够使用调用运算符和下标运算符。
问题在于 const OptPieceRef 的重载编译失败。使用 const_cast 可以;但那没有意义。非 const OptPieceRef 的重载不需要这些花招,而且我一直很坚信 const 函数和对象不会在它们各自的成员变量上赋予任何 const 性质。我一直以为类的实现者可以任意地去做,只不过 const 对象只能调用 const 函数。
我有两个疑问,分别是前文提到的为什么,以及我是否引入了未定义行为?我不认为自己引入过未定义行为,因为除了代码能够工作之外,这个Stack Overflow的回答 也说,只要底层对象不是 const,const_cast 就可以。话虽如此,我还是想确认。
为了使对虚拟的(口语中的说法,而非关键字)Piece 结构体的成员有更直接的访问能力(关于程序员接口方面),我添加了一个 PieceRef 类,其方式类似于Remy Lebeau的建议。
#include "Chess.h"
#include <iostream>
int main() {
Chess::Piece piece = Chess::Piece();
piece.color = Chess::Piece::Color::White;
piece.kind = Chess::Piece::Kind::Rook;
Chess chess = Chess();
chess.m_board(3, 3) = piece;
std::cout << "1. " << chess.m_board(3, 3).has_value() << std::endl;
std::cout << "2. " << chess.m_board(3, 4).has_value() << std::endl;
std::cout << "3. " << (chess.m_board(3, 3).value().kind == Chess::Piece::Kind::Rook) << std::endl;
std::cout << "4. " << (chess.m_board(3, 3).value().kind == Chess::Piece::Kind::Knight) << std::endl;
std::cout << "5. " << (chess.m_board(3, 3).value().color == Chess::Piece::Color::White) << std::endl;
std::cout << "6. " << (chess.m_board(3, 3).value().color == Chess::Piece::Color::Black) << std::endl;
std::cout << "7. " << (chess.m_board(3, 3) == chess.m_board(3, 3)) << std::endl;
std::cout << "8. " << (chess.m_board(3, 3) == chess.m_board(3, 4)) << std::endl;
std::cout << "9. " << (chess.m_board(4, 3) == chess.m_board(3, 4)) << std::endl;
std::cout << "10. " << (chess.m_board(3, 3).value() == chess.m_board(3, 3).value()) << std::endl;
}
#pragma once
#include <optional>
#include <cstdint>
#include <cstddef>
class Chess {
// Public for testing
public: struct Piece {
enum class Color { White, Black };
enum class Kind { King, Queen, Rook, Bishop, Knight, Pawn };
Color color;
Kind kind;
bool operator ==(const Piece& piece)const;
};
// Public for testing
public: class PieceRef {
public: class ColorRef {
private: uint32_t& m_array;
private: const size_t m_index;
public: ColorRef() = delete;
public: explicit ColorRef(uint32_t& array, const size_t& index);
public: ColorRef(ColorRef& color_ref);
public: ColorRef& operator =(const Piece::Color& color);
public: ColorRef& operator =(const ColorRef& color_ref);
public: operator Piece::Color()const;
};
public: class KindRef {
private: uint32_t& m_array;
private: const size_t m_index;
public: KindRef() = delete;
public: explicit KindRef(uint32_t& array, const size_t& index);
public: KindRef(KindRef& kind_ref);
public: KindRef& operator =(const Piece::Kind& kind);
public: KindRef& operator =(const KindRef& kind_ref);
public: operator Piece::Kind()const;
};
public: ColorRef color;
public: KindRef kind;
private: uint32_t& m_array;
private: const size_t m_index;
public: PieceRef() = delete;
public: explicit PieceRef(uint32_t& array, const size_t& index);
public: PieceRef(PieceRef& piece_ref);
public: PieceRef& operator =(const Piece& piece);
public: PieceRef& operator =(const PieceRef& piece_ref);
public: bool operator ==(const PieceRef& piece_ref)const;
public: operator Piece()const;
};
// Public for testing
public: class OptPieceRef {
private: uint32_t& m_array;
private: const size_t m_index;
public: OptPieceRef() = delete;
public: explicit OptPieceRef(uint32_t& array, const size_t& index);
public: OptPieceRef(OptPieceRef& opt_piece_ref);
public: OptPieceRef& operator =(const std::optional<Piece>& opt_piece);
public: OptPieceRef& operator =(const OptPieceRef& opt_piece_ref);
public: bool operator ==(const OptPieceRef& opt_piece_ref)const;
public: bool has_value()const;
public: PieceRef value();
public: const PieceRef value()const;
public: operator std::optional<Piece>()const;
};
private: class Board {
private: uint32_t m_array[8];
public: Board();
public: OptPieceRef operator ()(const size_t& row, const size_t& col);
public: const OptPieceRef operator ()(const size_t& row, const size_t& col)const;
};
// Public for testing
public: Board m_board;
};
#include "Chess.cpp"
#include <optional>
#include <cstdint>
#include <cstddef>
bool Chess::Piece::operator ==(const Piece& piece) const {
return color == piece.color & kind == piece.kind;
}
Chess::PieceRef::ColorRef::ColorRef(uint32_t& array, const size_t& index):
m_array(array),
m_index(index) {}
Chess::PieceRef::ColorRef::ColorRef(ColorRef& color_ref):
m_array(color_ref.m_array),
m_index(color_ref.m_index) {}
Chess::PieceRef::ColorRef& Chess::PieceRef::ColorRef::operator =(const Piece::Color& color) {
Piece::Kind kind;
{
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
kind = static_cast<Piece::Kind>(key % 6);
}
uint32_t key = 6 * static_cast<uint32_t>(color) + static_cast<uint32_t>(kind);
m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));
return *this;
}
Chess::PieceRef::ColorRef& Chess::PieceRef::ColorRef::operator =(const ColorRef& color_ref) {
Piece::Color color;
{
uint32_t key = (color_ref.m_array >> (4 * color_ref.m_index)) & 0b1111;
color = static_cast<Piece::Color>(key / 6);
}
Piece::Kind kind;
{
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
kind = static_cast<Piece::Kind>(key % 6);
}
uint32_t key = 6 * static_cast<uint32_t>(color) + static_cast<uint32_t>(kind);
m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));
return *this;
}
Chess::PieceRef::ColorRef::operator Piece::Color() const {
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
return static_cast<Piece::Color>(key / 6);
}
Chess::PieceRef::KindRef::KindRef(uint32_t& array, const size_t& index):
m_array(array),
m_index(index) {}
Chess::PieceRef::KindRef::KindRef(KindRef& kind_ref):
m_array(kind_ref.m_array),
m_index(kind_ref.m_index) {}
Chess::PieceRef::KindRef& Chess::PieceRef::KindRef::operator =(const Piece::Kind& kind) {
Piece::Color color;
{
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
color = static_cast<Piece::Color>(key / 6);
}
uint32_t key = 6 * static_cast<uint32_t>(color) + static_cast<uint32_t>(kind);
m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));
return *this;
}
Chess::PieceRef::KindRef& Chess::PieceRef::KindRef::operator =(const KindRef& kind_ref) {
Piece::Color color;
{
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
color = static_cast<Piece::Color>(key / 6);
}
Piece::Kind kind;
{
uint32_t key = (kind_ref.m_array >> (4 * kind_ref.m_index)) & 0b1111;
kind = static_cast<Piece::Kind>(key % 6);
}
uint32_t key = 6 * static_cast<uint32_t>(color) + static_cast<uint32_t>(kind);
m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));
return *this;
}
Chess::PieceRef::KindRef::operator Piece::Kind() const {
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
return static_cast<Piece::Kind>(key % 6);
}
Chess::PieceRef::PieceRef(uint32_t& array, const size_t& index):
color(array, index),
kind(array, index),
m_array(array),
m_index(index) {}
Chess::PieceRef::PieceRef(PieceRef& piece_ref):
color(piece_ref.m_array, piece_ref.m_index),
kind(piece_ref.m_array, piece_ref.m_index),
m_array(piece_ref.m_array),
m_index(piece_ref.m_index) {}
Chess::PieceRef& Chess::PieceRef::operator =(const Piece& piece) {
uint32_t key = 6 * static_cast<uint32_t>(piece.color) + static_cast<uint32_t>(piece.kind);
m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));
return *this;
}
Chess::PieceRef& Chess::PieceRef::operator =(const PieceRef& piece_ref) {
uint32_t key = (piece_ref.m_array >> (4 * piece_ref.m_index)) & 0b1111;
m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));
return *this;
}
bool Chess::PieceRef::operator ==(const PieceRef& piece_ref) const {
return ((m_array >> (4 * m_index)) & 0b1111) == ((piece_ref.m_array >> (4 * piece_ref.m_index)) & 0b1111);
}
Chess::PieceRef::operator Piece() const {
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
Piece piece = Piece();
{
piece.color = static_cast<Piece::Color>(key / 6);
piece.kind = static_cast<Piece::Kind>(key % 6);
}
return piece;
}
Chess::OptPieceRef::OptPieceRef(uint32_t& array, const size_t& index):
m_array(array),
m_index(index) {}
Chess::OptPieceRef::OptPieceRef(OptPieceRef& opt_piece_ref):
m_array(opt_piece_ref.m_array),
m_index(opt_piece_ref.m_index) {}
Chess::OptPieceRef& Chess::OptPieceRef::operator =(const std::optional<Piece>& opt_piece) {
uint32_t key = !opt_piece.has_value() ? 12 : 6 * static_cast<uint32_t>(opt_piece.value().color) + static_cast<uint32_t>(opt_piece.value().kind);
m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));
return *this;
}
Chess::OptPieceRef& Chess::OptPieceRef::operator =(const OptPieceRef& opt_piece_ref) {
uint32_t key = (opt_piece_ref.m_array >> (4 * opt_piece_ref.m_index)) & 0b1111;
m_array = m_array & ~(UINT32_C(0b1111) << (4 * m_index)) | (key << (4 * m_index));
return *this;
}
bool Chess::OptPieceRef::operator ==(const OptPieceRef& opt_piece_ref) const {
return ((m_array >> (4 * m_index)) & 0b1111) == ((opt_piece_ref.m_array >> (4 * opt_piece_ref.m_index)) & 0b1111);
}
bool Chess::OptPieceRef::has_value() const {
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
return key != 12;
}
Chess::PieceRef Chess::OptPieceRef::value() {
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
if (key == 12) {
throw std::bad_optional_access();
}
return PieceRef(m_array, m_index);
}
const Chess::PieceRef Chess::OptPieceRef::value() const {
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
if (key == 12) {
throw std::bad_optional_access();
}
return PieceRef(m_array, m_index);
}
Chess::OptPieceRef::operator std::optional<Piece>() const {
uint32_t key = (m_array >> (4 * m_index)) & 0b1111;
if (key == 12) {
return {};
}
Piece piece = Piece();
{
piece.color = static_cast<Piece::Color>(key / 6);
piece.kind = static_cast<Piece::Kind>(key % 6);
}
return piece;
}
Chess::Board::Board() {
for (size_t index = 0; index < 8; index++) {
m_array[index] = UINT32_C(0xCCCCCCCC);
}
}
Chess::OptPieceRef Chess::Board::operator ()(const size_t& row, const size_t& col) {
return OptPieceRef(m_array[row], col);
}
const Chess::OptPieceRef Chess::Board::operator ()(const size_t& row, const size_t& col) const {
// Requires const_cast
return OptPieceRef(const_cast<uint32_t&>(m_array[row]), col);
}
解决方案
如果你对这一设计坚持下去,你应该为 ConstPieceRef 等等创建类。你不应该能够从 const Board 构造出一个 PieceRef。
你也应该重新考虑 operator=(PieceRef),以及把引用作为数据成员。做任意一项都会使这些类型不再是 regular 的。赋值运算符尤其危险,因为从语法上看该类型似乎是常规的,但如果有人使用它,底层的 Board 会造成混乱。
如果你的目标是C++20或更高版本,可以在几乎不需要修改的情况下实现这一点;在那之前,你可以通过继承来实现类似的效果。
template <bool isConst>
class OptPieceRef {
using array_t = std::conditional_t<isConst, const uint32_t&, uint32_t&>;
private: array_t m_array;
private: size_t m_index;
public: OptPieceRef() = delete;
public: explicit OptPieceRef(array_t array, size_t index);
public: OptPieceRef(OptPieceRef& opt_piece_ref);
public: OptPieceRef& operator =(const std::optional<Piece>& opt_piece) requires (!isConst);
public: OptPieceRef& operator =(const OptPieceRef& opt_piece_ref) requires (!isConst);
public: bool operator ==(const OptPieceRef& opt_piece_ref)const;
public: bool has_value()const;
public: PieceRef<false> value();
public: PieceRef<true> value() const;
public: operator std::optional<Piece>()const;
};
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。