Qt6 でハマった件（２）

# はじめに
　前回、Python で PyQt6 を使っていたときにハマった話を紹介したが、

https://qiita.com/yynet2022/items/981503befb8361ed662e

　ちょっと気になったので C++ で直に Qt6 のライブラリを使った場合を確認してみたという話。

　先に結論を書いておくが、結果は同じ。（考えてみれば当たり前なんだろうけど）

- ResizeToContents を設定している QHeaderView を使った QTableView で、アイテム数の少ないモデルに切り替えた際、Out of range を起こす。

　したがって、新たな情報はあまり無いが、メモを残しておく。

# 確認環境
OS: Ubuntu 22.04.2 (WSL2: Windows 11 Home (22H2))
Qt: 6.5.2 (自分でインストール）

# 確認用コード
　例によって動かすことを主眼に、シンプルに書いているので、かなり簡素化したコードになっている。少なくともコーディングスタイルのお手本にはけっしてならない類のモノだとは、先にお断りしておく。

```c++:main.h
// -*- coding: utf-8 -*-
#ifndef MAIN_H
#define MAIN_H

#include <vector>
#include <string>

#include <QMainWindow>
#include <QMenuBar>
#include <QTableView>
#include <QHeaderView>
#include <QAbstractTableModel>

class _Model: public QAbstractTableModel
{
  Q_OBJECT
public:
  using items_type = std::vector<std::vector<std::string> >;
  using headers_type = std::vector<std::string>;

  _Model(items_type const &items, headers_type const &headers)
    : _items(items), _headers(headers) {}
  ~_Model() override = default;

  int rowCount(const QModelIndex &parent = QModelIndex()) const {
    return _items.size(); }
  int columnCount(const QModelIndex &parent = QModelIndex()) const {
    return _headers.size(); }
  QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
  QVariant headerData(int section, Qt::Orientation orientation,
		      int role = Qt::DisplayRole) const;
private:
  items_type _items;
  headers_type _headers;
};

class _HHeader: public QHeaderView
{
  Q_OBJECT
public:
  _HHeader(QWidget *parent = nullptr)
    : QHeaderView(Qt::Horizontal, parent) {
    setSectionResizeMode(ResizeMode::ResizeToContents);
  }
  ~_HHeader() override = default;
};

class _Table: public QTableView
{
  Q_OBJECT
public:
  _Table(QWidget *parent = nullptr)
    : QTableView(parent) {
    setHorizontalHeader(new _HHeader(this));
  }
  ~_Table() override = default;
};

class Main: public QMainWindow
{
  Q_OBJECT
public:
  Main();
  ~Main() override = default;
public slots:
  void setModel1();
  void setModel2a();
  void setModel2b();
private:
  void _setupUI();
  QTableView *tv;
  QAbstractTableModel *m1, *m2;
};

#endif
```

```c++:main.cxx
// -*- coding: utf-8 -*-
#include <iostream>
#include <QApplication>
#include "main.h"

QVariant _Model::data(const QModelIndex &index, int role) const
{
  if (role == Qt::DisplayRole) {
    auto nrow = rowCount();
    auto ncol = columnCount();
    if (index.row () >= nrow || index.column() >= ncol) {
      std::cout << "Model::data(" << index.row() << "," << index.column()
		<< "), role: " << role << std::endl;
      return QVariant();
    }
    return QString(_items[index.row()][index.column()].c_str());
  }
  return QVariant();
}

QVariant _Model::headerData(int section, Qt::Orientation orientation,
			    int role) const
{
  if (orientation == Qt::Horizontal &&
      role == Qt::DisplayRole) {
    auto ncol = columnCount();
    if (section >= ncol) {
      std::cout << "Model::section: " << section
		<< ", columnCont: " << ncol
		<< ", orientation: " << orientation
		<< ", role: " << role << std::endl;
      return QVariant();
    }
    return QString(_headers[section].c_str());
  }
  return QAbstractTableModel::headerData(section, orientation, role);
}

Main::Main()
{
  _setupUI();
  m1 = new _Model({{"a1", "b1", "c1"}, {"a2", "b2", "c2"}},
		   {"#1", "#2", "#3"});
  m2 = new _Model({}, {});
}

void Main::_setupUI()
{
  setWindowTitle("test");
  resize(600, 400);

  auto filemenu = menuBar()->addMenu("テスト(&T)");

  connect(filemenu->addAction("Model #1"), &QAction::triggered,
	  this, &Main::setModel1);
  connect(filemenu->addAction("Model #2a"), &QAction::triggered,
	  this, &Main::setModel2a);
  connect(filemenu->addAction("Model #2b"), &QAction::triggered,
	  this, &Main::setModel2b);
  {
    auto act = filemenu->addAction("閉じる(&X) ");
    act->setShortcut(tr("Ctrl+Q"));
    act->setStatusTip("ウィンドウを閉じる");
    connect(act, &QAction::triggered, this, &Main::close);
  }

  tv = new _Table(this);
  setCentralWidget(tv);
}

void Main::setModel1()
{
  std::cout << "setModel1" << std::endl;
  tv->setModel(m1);
}

void Main::setModel2a()
{
  std::cout << "setModel2a" << std::endl;
  tv->setModel(m2);
}

void Main::setModel2b()
{
  std::cout << "setModel2b" << std::endl;
  tv->setModel(nullptr);
  tv->setModel(m2);
}

int main(int argv, char *args[])
{
    QApplication app(argv, args);
    Main w;
    w.show();
    return app.exec();
}
```

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.16)

project(Qt6TableTest1 VERSION 0.0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)

add_executable(a.out main.cxx)
target_link_libraries(a.out PRIVATE Qt6::Widgets)
```

# 動作確認

__1.__ コンパイルから起動までは以下のように実行すればできる（ハズ）。

```shell-session:実行例
$ cmake -S . -B build
$ cmake --build build/
$ ./build/a.out
```

__2.__ ウィンドウが立ち上がったら、メニューの [テスト] から [Model #1] を選ぶ。
　　⇒縦２，横３のテーブルが表示される。


__3.__ メニューの [テスト] から [Model #2a]を選ぶ。
　　⇒エラー発生。以下のようなメッセージが表示されるハズ。


```shell-session:実行例（続き）
$ ./build/a.out
setModel1
setModel2a
Model::section: 0, columnCont: 0, orientation: 1, role: 0
Model::section: 1, columnCont: 0, orientation: 1, role: 0
Model::section: 2, columnCont: 0, orientation: 1, role: 0
```

（あ、スペルミスがある…… orz）

# 終わりに
　回避策としては、いったん __nullptr__ を setModel に入れれば良いことはわかっている。
　なので、これ以上追求するモチベーションはあまり無いのだけれど、もしかしたら Qt のコードを追ってデバッグしてみるのも一興かも？