diff --git a/xcursortheme.cpp b/xcursortheme.cpp
index f1ef1f35eb..d0893ea82a 100644
--- a/xcursortheme.cpp
+++ b/xcursortheme.cpp
@@ -18,38 +18,74 @@
 #include "xcursortheme.h"
 #include "3rdparty/xcursor.h"
 
+#include <QMap>
+#include <QSharedData>
+
 namespace KWin
 {
 
-KXcursorSprite::KXcursorSprite()
+class KXcursorSpritePrivate : public QSharedData
 {
+public:
+    QImage data;
+    QPoint hotspot;
+    std::chrono::milliseconds delay;
+};
+
+class KXcursorThemePrivate : public QSharedData
+{
+public:
+    QMap<QByteArray, QVector<KXcursorSprite>> registry;
+    qreal devicePixelRatio = 1;
+};
+
+KXcursorSprite::KXcursorSprite()
+    : d(new KXcursorSpritePrivate)
+{
+}
+
+KXcursorSprite::KXcursorSprite(const KXcursorSprite &other)
+    : d(other.d)
+{
+}
+
+KXcursorSprite::~KXcursorSprite()
+{
+}
+
+KXcursorSprite &KXcursorSprite::operator=(const KXcursorSprite &other)
+{
+    d = other.d;
+    return *this;
 }
 
 KXcursorSprite::KXcursorSprite(const QImage &data, const QPoint &hotspot,
                                const std::chrono::milliseconds &delay)
-    : m_data(data)
-    , m_hotspot(hotspot)
-    , m_delay(delay)
+    : d(new KXcursorSpritePrivate)
 {
+    d->data = data;
+    d->hotspot = hotspot;
+    d->delay = delay;
 }
 
 QImage KXcursorSprite::data() const
 {
-    return m_data;
+    return d->data;
 }
 
 QPoint KXcursorSprite::hotspot() const
 {
-    return m_hotspot;
+    return d->hotspot;
 }
 
 std::chrono::milliseconds KXcursorSprite::delay() const
 {
-    return m_delay;
+    return d->delay;
 }
 
 static void load_callback(XcursorImages *images, void *data)
 {
+    KXcursorThemePrivate *themePrivate = static_cast<KXcursorThemePrivate *>(data);
     QVector<KXcursorSprite> sprites;
 
     for (int i = 0; i < images->nimage; ++i) {
@@ -63,34 +99,53 @@ static void load_callback(XcursorImages *images, void *data)
         sprites.append(KXcursorSprite(data, hotspot, delay));
     }
 
-    auto cursorRegistry = static_cast<QMap<QByteArray, QVector<KXcursorSprite>> *>(data);
-    cursorRegistry->insert(images->name, sprites);
-
+    themePrivate->registry.insert(images->name, sprites);
     XcursorImagesDestroy(images);
 }
 
+KXcursorTheme::KXcursorTheme()
+    : d(new KXcursorThemePrivate)
+{
+}
+
+KXcursorTheme::KXcursorTheme(const KXcursorTheme &other)
+    : d(other.d)
+{
+}
+
+KXcursorTheme::~KXcursorTheme()
+{
+}
+
+KXcursorTheme &KXcursorTheme::operator=(const KXcursorTheme &other)
+{
+    d = other.d;
+    return *this;
+}
+
 qreal KXcursorTheme::devicePixelRatio() const
 {
-    return m_devicePixelRatio;
+    return d->devicePixelRatio;
 }
 
 bool KXcursorTheme::isEmpty() const
 {
-    return m_cursorRegistry.isEmpty();
+    return d->registry.isEmpty();
 }
 
 QVector<KXcursorSprite> KXcursorTheme::shape(const QByteArray &name) const
 {
-    return m_cursorRegistry.value(name);
+    return d->registry.value(name);
 }
 
 KXcursorTheme KXcursorTheme::fromTheme(const QString &themeName, int size, qreal dpr)
 {
     KXcursorTheme theme;
-    theme.m_devicePixelRatio = dpr;
+    KXcursorThemePrivate *themePrivate = theme.d;
+    themePrivate->devicePixelRatio = dpr;
 
     const QByteArray nativeThemeName = themeName.toUtf8();
-    xcursor_load_theme(nativeThemeName, size * dpr, load_callback, &theme.m_cursorRegistry);
+    xcursor_load_theme(nativeThemeName, size * dpr, load_callback, themePrivate);
 
     return theme;
 }
diff --git a/xcursortheme.h b/xcursortheme.h
index e53e1ca7b4..0436d87e2e 100644
--- a/xcursortheme.h
+++ b/xcursortheme.h
@@ -20,7 +20,7 @@
 #include <kwin_export.h>
 
 #include <QImage>
-#include <QMap>
+#include <QSharedDataPointer>
 #include <QVector>
 
 #include <chrono>
@@ -28,6 +28,9 @@
 namespace KWin
 {
 
+class KXcursorSpritePrivate;
+class KXcursorThemePrivate;
+
 /**
  * The KXcursorSprite class represents a single sprite in the Xcursor theme.
  */
@@ -39,12 +42,27 @@ public:
      */
     KXcursorSprite();
 
+    /**
+     * Constructs a copy of the KXcursorSprite object @a other.
+     */
+    KXcursorSprite(const KXcursorSprite &other);
+
     /**
      * Constructs an XcursorSprite with the specified @a data, @a hotspot, and @a delay.
      */
     KXcursorSprite(const QImage &data, const QPoint &hotspot,
                    const std::chrono::milliseconds &delay);
 
+    /**
+     * Destructs the KXcursorSprite object.
+     */
+    ~KXcursorSprite();
+
+    /**
+     * Assigns the value of @a other to the Xcursor sprite object.
+     */
+    KXcursorSprite &operator=(const KXcursorSprite &other);
+
     /**
      * Returns the image for this sprite.
      */
@@ -63,9 +81,7 @@ public:
     std::chrono::milliseconds delay() const;
 
 private:
-    QImage m_data;
-    QPoint m_hotspot;
-    std::chrono::milliseconds m_delay;
+    QSharedDataPointer<KXcursorSpritePrivate> d;
 };
 
 /**
@@ -74,6 +90,26 @@ private:
 class KWIN_EXPORT KXcursorTheme
 {
 public:
+    /**
+     * Constructs an empty Xcursor theme.
+     */
+    KXcursorTheme();
+
+    /**
+     * Constructs a copy of the KXcursorTheme object @a other.
+     */
+    KXcursorTheme(const KXcursorTheme &other);
+
+    /**
+     * Destructs the KXcursorTheme object.
+     */
+    ~KXcursorTheme();
+
+    /**
+     * Assigns the value of @a other to the Xcursor theme object.
+     */
+    KXcursorTheme &operator=(const KXcursorTheme &other);
+
     /**
      * Returns the ratio between device pixels and logical pixels for the Xcursor theme.
      */
@@ -95,8 +131,7 @@ public:
     static KXcursorTheme fromTheme(const QString &themeName, int size, qreal dpr);
 
 private:
-    QMap<QByteArray, QVector<KXcursorSprite>> m_cursorRegistry;
-    qreal m_devicePixelRatio = 1;
+    QSharedDataPointer<KXcursorThemePrivate> d;
 };
 
 } // namespace KWin